@@ -1636,6 +1636,7 @@ export class C2DEngineDocker extends C2DEngine {
16361636 // create the container
16371637 const mountVols : any = { '/data' : { } }
16381638 const hostConfig : HostConfig = {
1639+ NetworkMode : 'none' , // no network inside the container
16391640 Mounts : [
16401641 {
16411642 Type : 'volume' ,
@@ -1694,9 +1695,11 @@ export class C2DEngineDocker extends C2DEngine {
16941695 if ( advancedConfig . SecurityOpt )
16951696 containerInfo . HostConfig . SecurityOpt = advancedConfig . SecurityOpt
16961697 if ( advancedConfig . Binds ) containerInfo . HostConfig . Binds = advancedConfig . Binds
1698+ containerInfo . HostConfig . CapDrop = [ 'ALL' ]
1699+ for ( const cap of advancedConfig . CapDrop ?? [ ] ) {
1700+ containerInfo . HostConfig . CapDrop . push ( cap )
1701+ }
16971702 if ( advancedConfig . CapAdd ) containerInfo . HostConfig . CapAdd = advancedConfig . CapAdd
1698- if ( advancedConfig . CapDrop )
1699- containerInfo . HostConfig . CapDrop = advancedConfig . CapDrop
17001703 if ( advancedConfig . IpcMode )
17011704 containerInfo . HostConfig . IpcMode = advancedConfig . IpcMode
17021705 if ( advancedConfig . ShmSize )
@@ -2337,6 +2340,9 @@ export class C2DEngineDocker extends C2DEngine {
23372340 const job = JSON . parse ( JSON . stringify ( originaljob ) ) as DBComputeJob
23382341 const imageLogFile =
23392342 this . getC2DConfig ( ) . tempFolder + '/' + job . jobId + '/data/logs/image.log'
2343+ const controller = new AbortController ( )
2344+ const timeoutMs = 5 * 60 * 1000
2345+ const timer = setTimeout ( ( ) => controller . abort ( ) , timeoutMs )
23402346 try {
23412347 const pack = tarStream . pack ( )
23422348
@@ -2350,52 +2356,104 @@ export class C2DEngineDocker extends C2DEngine {
23502356 }
23512357 pack . finalize ( )
23522358
2353- // Build the image using the tar stream as context
2354- const buildStream = await this . docker . buildImage ( pack , {
2355- t : job . containerImage
2356- } )
2357-
2358- // Optional: listen to build output
2359- buildStream . on ( 'data' , ( data ) => {
2359+ // Build the image using the tar stream as context (Node IncomingMessage extends stream.Readable)
2360+ const buildStream = ( await this . docker . buildImage ( pack , {
2361+ t : job . containerImage ,
2362+ memory : 1024 * 1024 * 1024 , // 1GB RAM in bytes
2363+ memswap : - 1 , // Disable swap
2364+ cpushares : 512 , // CPU Shares (default is 1024)
2365+ cpuquota : 50000 , // 50% of one CPU (100000 = 1 CPU)
2366+ cpuperiod : 100000 , // Default period
2367+ nocache : true , // prevent cache poison
2368+ abortSignal : controller . signal
2369+ } ) ) as Readable
2370+
2371+ const onBuildData = ( data : Buffer ) => {
23602372 try {
23612373 const text = JSON . parse ( data . toString ( 'utf8' ) )
2362- CORE_LOGGER . debug (
2363- "Building image for jobId '" + job . jobId + "': " + text . stream . trim ( )
2364- )
2365- appendFileSync ( imageLogFile , String ( text . stream ) )
2374+ if ( text && text . stream && typeof text . stream === 'string' ) {
2375+ CORE_LOGGER . debug (
2376+ "Building image for jobId '" + job . jobId + "': " + text . stream . trim ( )
2377+ )
2378+ appendFileSync ( imageLogFile , String ( text . stream ) )
2379+ }
23662380 } catch ( e ) {
23672381 // console.log('non json build data: ', data.toString('utf8'))
23682382 }
2369- } )
2383+ }
2384+ buildStream . on ( 'data' , onBuildData )
23702385
23712386 await new Promise < void > ( ( resolve , reject ) => {
2372- buildStream . on ( 'end' , ( ) => {
2373- CORE_LOGGER . debug ( `Image '${ job . containerImage } ' built successfully.` )
2374- this . updateImageUsage ( job . containerImage ) . catch ( ( e ) => {
2375- CORE_LOGGER . debug ( `Failed to track image usage: ${ e . message } ` )
2387+ let settled = false
2388+ const detachBuildLog = ( ) => {
2389+ buildStream . removeListener ( 'data' , onBuildData )
2390+ }
2391+ const finish = ( action : ( ) => void ) => {
2392+ if ( settled ) return
2393+ settled = true
2394+ action ( )
2395+ }
2396+ const onAbort = ( ) => {
2397+ finish ( ( ) => {
2398+ detachBuildLog ( )
2399+ buildStream . destroy ( )
2400+ const err = new Error ( 'Image build aborted' ) as NodeJS . ErrnoException
2401+ err . code = 'ABORT_ERR'
2402+ err . name = 'AbortError'
2403+ reject ( err )
23762404 } )
2377- resolve ( )
2378- } )
2405+ }
2406+ controller . signal . addEventListener ( 'abort' , onAbort , { once : true } )
2407+ const onSuccess = ( ) => {
2408+ finish ( ( ) => {
2409+ detachBuildLog ( )
2410+ controller . signal . removeEventListener ( 'abort' , onAbort )
2411+ CORE_LOGGER . debug ( `Image '${ job . containerImage } ' built successfully.` )
2412+ this . updateImageUsage ( job . containerImage ) . catch ( ( e ) => {
2413+ CORE_LOGGER . debug ( `Failed to track image usage: ${ e . message } ` )
2414+ } )
2415+ resolve ( )
2416+ } )
2417+ }
2418+ // Some HTTP responses emit `close` without a reliable `end`; handle both (settled ensures once).
2419+ buildStream . on ( 'end' , onSuccess )
2420+ buildStream . on ( 'close' , onSuccess )
23792421 buildStream . on ( 'error' , ( err ) => {
23802422 CORE_LOGGER . debug ( `Error building image '${ job . containerImage } ':` + err . message )
23812423 appendFileSync ( imageLogFile , String ( err . message ) )
2382- reject ( err )
2424+ finish ( ( ) => {
2425+ detachBuildLog ( )
2426+ controller . signal . removeEventListener ( 'abort' , onAbort )
2427+ reject ( err )
2428+ } )
23832429 } )
23842430 } )
23852431 job . status = C2DStatusNumber . ConfiguringVolumes
23862432 job . statusText = C2DStatusText . ConfiguringVolumes
2387- this . db . updateJob ( job )
2433+ await this . db . updateJob ( job )
23882434 } catch ( err ) {
2389- CORE_LOGGER . error (
2390- `Unable to build docker image: ${ job . containerImage } : ${ err . message } `
2391- )
2392- appendFileSync ( imageLogFile , String ( err . message ) )
2435+ const aborted =
2436+ ( err as NodeJS . ErrnoException ) ?. code === 'ABORT_ERR' ||
2437+ ( err as Error ) ?. name === 'AbortError'
2438+ if ( aborted ) {
2439+ // timeout-specific handling
2440+ const msg = `Image build timed out after ${ timeoutMs / 1000 } s`
2441+ CORE_LOGGER . error ( `Unable to build docker image: ${ job . containerImage } : ${ msg } ` )
2442+ appendFileSync ( imageLogFile , msg )
2443+ } else {
2444+ CORE_LOGGER . error (
2445+ `Unable to build docker image: ${ job . containerImage } : ${ err . message } `
2446+ )
2447+ appendFileSync ( imageLogFile , String ( err . message ) )
2448+ }
23932449 job . status = C2DStatusNumber . BuildImageFailed
23942450 job . statusText = C2DStatusText . BuildImageFailed
23952451 job . isRunning = false
23962452 job . dateFinished = String ( Date . now ( ) / 1000 )
23972453 await this . db . updateJob ( job )
23982454 await this . cleanupJob ( job )
2455+ } finally {
2456+ clearTimeout ( timer )
23992457 }
24002458 }
24012459
0 commit comments