Skip to content

Commit d0782da

Browse files
authored
Merge pull request #14 from satackey/reduce_concurrency
Limit the concurrency of restoring and saving layers
2 parents acaa13b + 11c7535 commit d0782da

File tree

7 files changed

+53
-10
lines changed

7 files changed

+53
-10
lines changed

README.md

+10-5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ You can run `docker build` and `docker-compose build` in your GitHub Actions wor
77

88
This GitHub Action uses the [docker save](https://docs.docker.com/engine/reference/commandline/save/) / [docker load](https://docs.docker.com/engine/reference/commandline/load/) command and the [@actions/cache](https://www.npmjs.com/package/@actions/cache) library.
99

10+
## ⚠️ **Deprecation Notice for `v0.0.4` and older** ⚠️
11+
12+
The author had not taken into account that a large number of layers would be cached,
13+
so those versions processes all layers in parallel. ([#12](https://github.com/satackey/action-docker-layer-caching/issues/12))
14+
**Please update to version `v0.0.5` with limited concurrency to avoid overloading the cache service.**
1015

1116
## Example workflows
1217

@@ -30,11 +35,11 @@ jobs:
3035
# In this step, this action saves a list of existing images,
3136
# the cache is created without them in the post run.
3237
# It also restores the cache if it exists.
33-
- uses: satackey/[email protected].4
38+
- uses: satackey/[email protected].5
3439

3540
- run: docker-compose up --build
3641

37-
# Finally, "Post Run satackey/[email protected].4",
42+
# Finally, "Post Run satackey/[email protected].5",
3843
# which is the process of saving the cache, will be executed.
3944
```
4045

@@ -56,12 +61,12 @@ jobs:
5661
# In this step, this action saves a list of existing images,
5762
# the cache is created without them in the post run.
5863
# It also restores the cache if it exists.
59-
- uses: satackey/[email protected].4
64+
- uses: satackey/[email protected].5
6065

6166
- name: Build the Docker image
6267
run: docker build . --file Dockerfile --tag my-image-name:$(date +%s)
6368

64-
# Finally, "Post Run satackey/[email protected].4",
69+
# Finally, "Post Run satackey/[email protected].5",
6570
# which is the process of saving the cache, will be executed.
6671
```
6772

@@ -74,7 +79,7 @@ By default, the cache is separated by the workflow name.
7479
You can also set the cache key manually, like the official [actions/cache](https://github.com/actions/cache#usage) action.
7580

7681
```yaml
77-
- uses: satackey/[email protected].4
82+
- uses: satackey/[email protected].5
7883
with:
7984
key: foo-docker-cache-{hash}
8085
restore-keys: |

action.yml

+4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ inputs:
1414
description: An ordered list of keys to use for restoring the cache if no cache hit occurred for key
1515
required: false
1616
default: docker-layer-caching-${{ github.workflow }}-
17+
concurrency:
18+
description: The number of concurrency when restoring and saving layers
19+
required: true
20+
default: '4'
1721

1822
runs:
1923
using: node12

main.ts

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const main = async () => {
1111
core.saveState(`already-existing-images`, JSON.stringify(await new ImageDetector().getExistingImages()))
1212

1313
const layerCache = new LayerCache([])
14+
layerCache.concurrency = parseInt(core.getInput(`concurrency`, { required: true }), 10)
1415
const restoredKey = await layerCache.restore(primaryKey, restoreKeys)
1516
await layerCache.cleanUp()
1617

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"@actions/core": "^1.2.4",
1111
"actions-exec-listener": "^0.0.2",
1212
"crypto": "^1.0.1",
13+
"native-promise-pool": "^3.13.0",
1314
"string-format": "^2.0.0",
1415
"typescript-is": "^0.16.3"
1516
},

post.ts

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const main = async () => {
1818
await imageDetector.getExistingImages()
1919
core.debug(JSON.stringify({ imageIdsToSave: imageDetector.getImagesShouldSave() }))
2020
const layerCache = new LayerCache(imageDetector.getImagesShouldSave())
21+
layerCache.concurrency = parseInt(core.getInput(`concurrency`, { required: true }), 10)
2122

2223
layerCache.unformattedOrigianlKey = primaryKey
2324
core.debug(JSON.stringify({ restoredKey, formattedOriginalCacheKey: layerCache.getFormattedOriginalCacheKey()}))

src/LayerCache.ts

+31-5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { ExecOptions } from '@actions/exec/lib/interfaces'
77
import { promises as fs } from 'fs'
88
import { assertManifests, Manifest, Manifests } from './Tar'
99
import format from 'string-format'
10+
import PromisePool from 'native-promise-pool'
1011

1112
class LayerCache {
1213
// repotag: string
@@ -18,6 +19,7 @@ class LayerCache {
1819
enabledParallel = true
1920
// unpackedTarDir: string = ''
2021
// manifests: Manifests = []
22+
concurrency: number = 4
2123

2224
constructor(ids: string[]) {
2325
// this.repotag = repotag
@@ -108,7 +110,16 @@ class LayerCache {
108110
}
109111

110112
private async storeLayers(): Promise<number[]> {
111-
return await Promise.all((await this.getLayerIds()).map(layerId => this.storeSingleLayerBy(layerId)))
113+
const pool = new PromisePool(this.concurrency)
114+
115+
const result = Promise.all(
116+
(await this.getLayerIds()).map(
117+
layerId => {
118+
return pool.open(() => this.storeSingleLayerBy(layerId))
119+
}
120+
)
121+
)
122+
return result
112123
}
113124

114125
static async dismissCacheAlreadyExistsError(promise: Promise<number>): Promise<number> {
@@ -173,16 +184,31 @@ class LayerCache {
173184
}
174185

175186
private async restoreLayers() {
176-
const restoring = (await this.getLayerIds()).map(layerId => this.restoreSingleLayerBy(layerId));
177-
const restoredLayerKeysThatMayContainUndefined = await Promise.all(restoring)
187+
const pool = new PromisePool(this.concurrency)
188+
189+
const restoredLayerKeysThatMayContainUndefined = await Promise.all(
190+
(await this.getLayerIds()).map(
191+
layerId => {
192+
return pool.open(() => this.restoreSingleLayerBy(layerId))
193+
}
194+
)
195+
)
196+
178197
core.debug(JSON.stringify({ log: `restoreLayers`, restoredLayerKeysThatMayContainUndefined }))
179198
const FailedToRestore = (restored: string | undefined) => restored === undefined
180199
return restoredLayerKeysThatMayContainUndefined.filter(FailedToRestore).length === 0
181200
}
182201

183-
private async restoreSingleLayerBy(id: string): Promise<string | undefined> {
202+
private async restoreSingleLayerBy(id: string): Promise<string> {
184203
core.debug(JSON.stringify({ log: `restoreSingleLayerBy`, id }))
185-
return await cache.restoreCache([this.genSingleLayerStorePath(id)], this.genSingleLayerStoreKey(id))
204+
205+
const result = await cache.restoreCache([this.genSingleLayerStorePath(id)], this.genSingleLayerStoreKey(id))
206+
207+
if (result == null) {
208+
throw new Error(`Layer cache not found: ${JSON.stringify({ id })}`)
209+
}
210+
211+
return result
186212
}
187213

188214
private async loadImageFromUnpacked() {

yarn.lock

+5
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,11 @@ minimatch@^3.0.4:
329329
dependencies:
330330
brace-expansion "^1.1.7"
331331

332+
native-promise-pool@^3.13.0:
333+
version "3.13.0"
334+
resolved "https://registry.yarnpkg.com/native-promise-pool/-/native-promise-pool-3.13.0.tgz#faeaf9b4ee769a79328cc0435930079ad951b27b"
335+
integrity sha512-CnUhv57AXMyxGazhYrqhlhwRg1rG36ygrDlnaCa70jBOePhV+OLM9CZkUAgceTXO6BZEQ3F5GRRWwB+wSzI4Aw==
336+
332337
nested-error-stacks@^2:
333338
version "2.1.0"
334339
resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz#0fbdcf3e13fe4994781280524f8b96b0cdff9c61"

0 commit comments

Comments
 (0)