@@ -4,10 +4,10 @@ import Docker from 'dockerode'
4
4
export const docker = new Docker ( )
5
5
6
6
const MAX_SCRIPT_EXECUTION_TIME =
7
- Number ( process . env . MAX_SCRIPT_EXECUTION_TIME ) || 15000
7
+ Number ( process . env . MAX_SCRIPT_EXECUTION_TIME ) || 15000
8
8
9
9
function buildImage ( p , id , logStream , files ) {
10
- return new Promise ( ( resolve , reject ) => {
10
+ return new Promise ( ( resolve , reject ) => {
11
11
docker . buildImage (
12
12
{
13
13
context : path . join ( p ) ,
@@ -18,7 +18,6 @@ function buildImage(p, id, logStream, files) {
18
18
if ( err ) {
19
19
reject ( err )
20
20
}
21
- console . log ( 'buildImage1' )
22
21
stream . pipe ( logStream )
23
22
docker . modem . followProgress ( stream , ( err , res ) => {
24
23
if ( err ) {
@@ -28,108 +27,125 @@ function buildImage(p, id, logStream, files) {
28
27
} )
29
28
}
30
29
)
31
- } )
30
+ } )
32
31
}
33
32
34
33
function sanitizeContainerId ( id ) {
35
- return id . slice ( 0 , 8 )
34
+ return id . slice ( 0 , 8 )
36
35
}
37
36
38
37
// New cleanup function
39
- function cleanupContainerAndImage ( container , imageId , send ) {
40
- container . inspect ( ( err , data ) => {
41
- if ( err ) {
42
- console . error ( `Failed to inspect container: ${ err } ` )
43
- } else {
44
- const containerState = data . State . Status
38
+ async function cleanupContainerAndImage ( container , imageId , send ) {
39
+ try {
40
+ const data = await container . inspect ( )
45
41
46
- if ( containerState === 'running' ) {
47
- container . kill ( ( err ) => {
48
- if ( err ) {
49
- console . error ( `Failed to kill container: ${ err } ` )
50
- } else {
51
- send ( {
52
- type : 'debug' ,
53
- payload : `[system] Container ${ sanitizeContainerId ( container . id ) } killed.` ,
54
- channel : 'runtime' ,
55
- } )
56
- }
57
-
58
- removeContainerAndImage ( container , imageId , send )
42
+ if ( data . State . Status === 'running' ) {
43
+ try {
44
+ await container . kill ( )
45
+ send ( {
46
+ type : 'debug' ,
47
+ payload : `[system] Container ${ sanitizeContainerId (
48
+ container . id
49
+ ) } killed.`,
50
+ channel : 'runtime' ,
59
51
} )
60
- } else {
61
- removeContainerAndImage ( container , imageId , send )
52
+ } catch ( err ) {
53
+ console . error ( `Failed to kill container: ${ err } ` )
62
54
}
63
55
}
64
- } )
56
+
57
+ await removeContainer ( container , send )
58
+ await removeImageIfNoContainers ( imageId , send )
59
+ } catch ( err ) {
60
+ console . error ( `Failed to inspect container: ${ err } ` )
61
+ await removeContainer ( container , send )
62
+ await removeImageIfNoContainers ( imageId , send )
63
+ }
65
64
}
66
65
67
- function removeContainerAndImage ( container , imageId , send ) {
68
- container . remove ( ( err ) => {
69
- if ( err ) {
70
- console . error ( `Failed to remove container: ${ err } ` )
71
- } else {
66
+ async function removeImageIfNoContainers ( imageId , send ) {
67
+ try {
68
+ // List all containers using this image
69
+ const containers = await docker . listContainers ( {
70
+ all : true ,
71
+ filters : {
72
+ ancestor : [ imageId ] ,
73
+ } ,
74
+ } )
75
+
76
+ // Only remove the image if no containers are using it
77
+ if ( containers . length === 0 ) {
78
+ await docker . getImage ( imageId ) . remove ( { force : true } )
72
79
send ( {
73
80
type : 'debug' ,
74
- payload : `[system] Container ${ sanitizeContainerId ( container . id ) } removed.` ,
81
+ payload : `[system] Image ${ imageId } removed.` ,
75
82
channel : 'runtime' ,
76
83
} )
77
-
78
- docker . getImage ( imageId ) . remove ( ( err ) => {
79
- if ( err ) {
80
- console . error ( `Failed to remove image: ${ err } ` )
81
- } else {
82
- send ( {
83
- type : 'debug' ,
84
- payload : `[system] Image ${ imageId } removed.` ,
85
- channel : 'runtime' ,
86
- } )
87
- }
88
- } )
84
+ } else {
85
+ console . log (
86
+ `Image ${ imageId } still has ${ containers . length } containers, skipping removal`
87
+ )
89
88
}
90
- } )
89
+ } catch ( err ) {
90
+ console . error ( `Failed to remove image: ${ err } ` )
91
+ }
92
+ }
93
+
94
+ async function removeContainer ( container , send ) {
95
+ try {
96
+ await container . remove ( )
97
+ send ( {
98
+ type : 'debug' ,
99
+ payload : `[system] Container ${ sanitizeContainerId (
100
+ container . id
101
+ ) } removed.`,
102
+ channel : 'runtime' ,
103
+ } )
104
+ } catch ( err ) {
105
+ console . error ( `Failed to remove container: ${ err } ` )
106
+ }
91
107
}
92
108
93
109
function runContainer ( id , send , writeStream , context ) : Promise < boolean > {
94
- return new Promise ( async ( resolve , reject ) => {
110
+ return new Promise ( async ( resolve , reject ) => {
111
+ let isCleanedUp = false
112
+
95
113
send ( {
96
114
type : 'debug' ,
97
115
payload : '[system] Creating container...' ,
98
116
channel : 'runtime' ,
99
117
} )
100
118
101
- docker . createContainer ( { Image : id , name : id , Tty : true } ,
119
+ docker . createContainer (
120
+ { Image : id , name : id , Tty : true } ,
102
121
( err , container ) => {
103
122
if ( err ) {
104
123
return reject ( err )
105
124
}
106
125
107
- console . log ( 'container' , container )
108
-
109
126
let isRunning = false
110
-
111
127
const containerId = sanitizeContainerId ( container . id )
112
128
113
- const job = context . jobs [ context . socketId ]
114
- job . container = container
115
- job . onKill = ( ) => {
116
- isRunning = false
117
- cleanupContainerAndImage ( container , id , send )
118
- writeStream . end ( )
119
- resolve ( true )
129
+ // Promise-based cleanup function
130
+ const cleanup = async ( ) => {
131
+ if ( ! isCleanedUp ) {
132
+ isCleanedUp = true
133
+ isRunning = false
134
+
135
+ try {
136
+ writeStream . end ( )
137
+ await cleanupContainerAndImage ( container , id , send )
138
+ resolve ( true )
139
+ } catch ( error ) {
140
+ console . error ( 'Cleanup failed:' , error )
141
+ reject ( error )
142
+ }
143
+ }
120
144
}
121
145
122
- send ( {
123
- type : 'debug' ,
124
- payload : `[system] Container ${ containerId } created.` ,
125
- channel : 'runtime' ,
126
- } )
127
-
128
- send ( {
129
- type : 'debug' ,
130
- payload : `[system] Attaching WebSocket...` ,
131
- channel : 'runtime' ,
132
- } )
146
+ const job = context . jobs [ context . socketId ]
147
+ job . container = container
148
+ job . onKill = cleanup
133
149
134
150
container . attach (
135
151
{ stream : true , stdout : true , stderr : true } ,
@@ -138,68 +154,55 @@ function runContainer(id, send, writeStream, context): Promise<boolean> {
138
154
return reject ( err )
139
155
}
140
156
141
- send ( {
142
- type : 'debug' ,
143
- payload : `[system] WebSocket attached.` ,
144
- channel : 'runtime' ,
145
- } )
146
-
147
- // Pipe container stdout to client.
148
157
stream . pipe ( writeStream )
149
158
150
- send ( {
151
- type : 'debug' ,
152
- payload : `[system] Starting container ${ containerId } ...` ,
153
- channel : 'runtime' ,
154
- } )
155
-
156
159
// Listen for kill signal
157
- writeStream . onKill = ( ) => {
158
- setTimeout ( ( ) => {
159
- isRunning = false
160
+ writeStream . onKill = async ( ) => {
161
+ if ( ! isCleanedUp ) {
160
162
stream . unpipe ( writeStream )
161
- writeStream . end ( )
162
- cleanupContainerAndImage ( container , id , send )
163
- } , 1000 )
163
+ await cleanup ( )
164
+ }
164
165
}
165
166
166
- setTimeout ( ( ) => {
167
- if ( isRunning ) {
168
- cleanupContainerAndImage ( container , id , send )
167
+ // Set timeout for max execution time
168
+ const timeoutId = setTimeout ( async ( ) => {
169
+ if ( isRunning && ! isCleanedUp ) {
170
+ stream . unpipe ( writeStream )
171
+ await cleanup ( )
169
172
}
170
173
} , MAX_SCRIPT_EXECUTION_TIME )
171
174
172
175
// Start the container
173
176
container . start ( ( err ) => {
174
177
if ( err ) {
178
+ clearTimeout ( timeoutId )
175
179
return reject ( err )
176
180
}
177
181
178
182
isRunning = true
179
183
180
- send ( {
181
- type : 'debug' ,
182
- payload : `[system] Container ${ containerId } started.` ,
183
- channel : 'runtime' ,
184
- } )
184
+ // Wait for container to stop
185
+ container . wait ( async ( err , result ) => {
186
+ clearTimeout ( timeoutId )
185
187
186
- // Call the cleanup function when the container stops
187
- container . wait ( ( err ) => {
188
188
if ( err ) {
189
- console . error ( `Failed to wait for container to stop: ${ err } ` )
190
- } else {
191
- cleanupContainerAndImage ( container , id , send )
189
+ console . error ( `Failed to wait for container to stop: ${ err } ` )
190
+ }
191
+
192
+ if ( ! isCleanedUp ) {
193
+ stream . unpipe ( writeStream )
194
+ await cleanup ( )
192
195
}
193
196
} )
194
197
} )
195
198
}
196
199
)
197
200
}
198
201
)
199
- } )
202
+ } )
200
203
}
201
204
202
205
export default {
203
- buildImage,
204
- runContainer,
206
+ buildImage,
207
+ runContainer,
205
208
}
0 commit comments