@@ -14,6 +14,7 @@ import {
1414const mebibyte = 1024 ** 2
1515const minimumResolvedCpuLimit = 0.25
1616const minimumResolvedRamLimitMib = 512
17+ const minimumResolvedSwapLimitMib = 1
1718const precisionScale = 100
1819
1920type HostResources = {
@@ -24,12 +25,26 @@ type HostResources = {
2425export type ResolvedComposeResourceLimits = {
2526 readonly cpuLimit : number
2627 readonly ramLimit : string
28+ readonly swapLimit : string
2729}
2830
2931const cpuAbsolutePattern = / ^ \d + (?: \. \d + ) ? $ / u
32+ const ramLimitPattern = / ^ ( \d + (?: \. \d + ) ? ) ( b | k | k b | m | m b | g | g b | t | t b ) $ / iu
3033const ramAbsolutePattern = / ^ \d + (?: \. \d + ) ? (?: b | k | k b | m | m b | g | g b | t | t b ) $ / iu
3134const percentPattern = / ^ \d + (?: \. \d + ) ? % $ / u
3235
36+ const ramUnitMibFactors : Readonly < Record < string , number > > = {
37+ b : 1 / mebibyte ,
38+ k : 1 / 1024 ,
39+ kb : 1 / 1024 ,
40+ m : 1 ,
41+ mb : 1 ,
42+ g : 1024 ,
43+ gb : 1024 ,
44+ t : 1024 * 1024 ,
45+ tb : 1024 * 1024
46+ }
47+
3348const normalizePrecision = ( value : number ) : number => Math . round ( value * precisionScale ) / precisionScale
3449
3550const missingLimit = ( ) : string | undefined => undefined
@@ -134,6 +149,34 @@ const resolvePercentRamLimit = (percent: number, totalMemoryBytes: number): stri
134149 return `${ targetMib } m`
135150}
136151
152+ const parseRamLimitMib = ( value : string ) : number | null => {
153+ const match = ramLimitPattern . exec ( value )
154+ if ( match === null ) {
155+ return null
156+ }
157+
158+ const amount = Number ( match [ 1 ] ?? "0" )
159+ const unit = ( match [ 2 ] ?? "m" ) . toLowerCase ( )
160+ const factor = ramUnitMibFactors [ unit ]
161+ return ! Number . isFinite ( amount ) || amount <= 0 || factor === undefined
162+ ? null
163+ : amount * factor
164+ }
165+
166+ // CHANGE: allow project containers to use WSL swap without removing hard RAM limits
167+ // WHY: Docker Compose `memswap_limit` is RAM+swap total; setting it equal to RAM disables extra swap
168+ // SOURCE: n/a
169+ // FORMAT THEOREM: forall r: valid_ram(r) -> swap_limit(r) >= 2 * ram_limit(r)
170+ // PURITY: CORE
171+ // INVARIANT: generated containers keep a finite memory+swap ceiling
172+ // COMPLEXITY: O(1)/O(1)
173+ const resolveSwapLimit = ( ramLimit : string ) : string => {
174+ const ramMib = parseRamLimitMib ( ramLimit )
175+ return ramMib === null
176+ ? ramLimit
177+ : `${ Math . max ( minimumResolvedSwapLimitMib , Math . ceil ( ramMib * 2 ) ) } m`
178+ }
179+
137180export const resolveComposeResourceLimits = (
138181 template : Pick < TemplateConfig , "cpuLimit" | "ramLimit" > ,
139182 hostResources : HostResources
@@ -143,13 +186,16 @@ export const resolveComposeResourceLimits = (
143186 const cpuPercent = parsePercent ( cpuLimitIntent )
144187 const ramPercent = parsePercent ( ramLimitIntent )
145188
189+ const ramLimit = ramPercent === null
190+ ? ramLimitIntent
191+ : resolvePercentRamLimit ( ramPercent , hostResources . totalMemoryBytes )
192+
146193 return {
147194 cpuLimit : cpuPercent === null
148195 ? Number ( cpuLimitIntent )
149196 : resolvePercentCpuLimit ( cpuPercent , hostResources . cpuCount ) ,
150- ramLimit : ramPercent === null
151- ? ramLimitIntent
152- : resolvePercentRamLimit ( ramPercent , hostResources . totalMemoryBytes )
197+ ramLimit,
198+ swapLimit : resolveSwapLimit ( ramLimit )
153199 }
154200}
155201
0 commit comments