shiveshnavin commited on
Commit
00d8f4e
·
1 Parent(s): 6c23657

Fix stop render

Browse files
Files changed (1) hide show
  1. renderer.js +156 -37
renderer.js CHANGED
@@ -97,59 +97,167 @@ export async function doRender(
97
  const remotionArgs = ['-y', 'remotion', target, ...buildParams.trim().split(/\s+/).filter(arg => arg), renderComposition, outFile];
98
  const cmd = `npx ${remotionArgs.join(' ')}`;
99
  const spawnOptions = {
100
- detached: false,
101
- shell: true // Always use shell for proper parameter parsing
 
102
  };
103
 
104
- if (platform() === 'win32') {
 
105
  spawnOptions.detached = true;
106
  }
107
 
108
  const childProcess = spawn('npx', remotionArgs, spawnOptions);
109
  let isProcessKilled = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
 
111
  if (controller && controller.stop) {
112
- controller.stop = () => {
113
  console.log('Stopping render studio cli process');
114
- if (isProcessKilled || !childProcess.pid) {
115
- console.log('Process already terminated or no PID available');
 
 
 
 
 
 
 
116
  return;
117
  }
118
 
119
  try {
120
- // First try to kill the child process directly
121
- if (!childProcess.killed) {
122
- childProcess.kill('SIGTERM');
123
- isProcessKilled = true;
124
- console.log('Process terminated with SIGTERM');
125
- }
126
 
127
- // If detached, also try to kill the process group
128
- if (spawnOptions.detached) {
129
- setTimeout(() => {
130
- try {
131
- if (childProcess.pid) {
132
- process.kill(-childProcess.pid, 'SIGKILL');
133
- console.log('Process group killed with SIGKILL');
134
- }
135
- } catch (e) {
136
- // Ignore ESRCH errors - process already dead
137
- if (e.code !== 'ESRCH') {
138
- console.error('Failed to kill process group:', e.message);
 
 
 
 
 
 
139
  }
140
- }
141
- }, 1000);
142
- }
 
 
143
  } catch (e) {
144
- // Ignore ESRCH errors - process already dead
145
- if (e.code !== 'ESRCH') {
146
- console.error('Failed to kill process:', e.message);
147
- }
148
  }
149
  }
150
  }
151
 
152
  console.log('Starting video render. ' + cmd);
 
 
 
 
 
153
  let updateCounter = 0;
154
  childProcess.stdout.on('data', (data) => {
155
  sendToObserver(jobId, data);
@@ -166,27 +274,38 @@ export async function doRender(
166
  });
167
 
168
  return new Promise((resolve, reject) => {
169
- childProcess.on('close', (code) => {
170
  isProcessKilled = true; // Mark process as terminated
171
- console.log('Render video finished');
 
 
 
 
172
  sendToObserver(jobId, code === 0 ? 'completed' : 'failed');
173
  if (code === 0) {
174
  resolve(outFile);
175
  console.log(`'${target}' completed successfully.`);
176
  } else {
177
- reject({
178
- message: `'${target}' failed with code ${code}.`,
179
- });
180
- console.error(`'${target}' failed with code ${code}.`);
 
181
  }
182
  });
183
 
184
  childProcess.on('error', (error) => {
185
  isProcessKilled = true; // Mark process as terminated
 
186
  console.error('Child process error:', error);
187
  sendToObserver(jobId, 'failed');
188
  reject(error);
189
  });
 
 
 
 
 
190
  });
191
  }
192
 
 
97
  const remotionArgs = ['-y', 'remotion', target, ...buildParams.trim().split(/\s+/).filter(arg => arg), renderComposition, outFile];
98
  const cmd = `npx ${remotionArgs.join(' ')}`;
99
  const spawnOptions = {
100
+ detached: true, // Always detach to create new process group
101
+ shell: true, // Use shell for proper parameter parsing
102
+ stdio: ['ignore', 'pipe', 'pipe'] // Ensure we can capture output
103
  };
104
 
105
+ // On Unix systems, create a new process group for easier cleanup
106
+ if (platform() !== 'win32') {
107
  spawnOptions.detached = true;
108
  }
109
 
110
  const childProcess = spawn('npx', remotionArgs, spawnOptions);
111
  let isProcessKilled = false;
112
+ let processTreePids = new Set(); // Track all process PIDs for cleanup
113
+
114
+ // Function to find and kill all child processes
115
+ const killProcessTree = async (pid, signal = 'SIGTERM') => {
116
+ try {
117
+ if (platform() === 'win32') {
118
+ // On Windows, use taskkill to kill the entire process tree
119
+ const { exec } = await import('child_process');
120
+ return new Promise((resolve) => {
121
+ exec(`taskkill /pid ${pid} /t /f`, (error) => {
122
+ if (error && !error.message.includes('not found')) {
123
+ console.error('Failed to kill Windows process tree:', error.message);
124
+ } else {
125
+ console.log(`Windows process tree ${pid} killed`);
126
+ }
127
+ resolve();
128
+ });
129
+ });
130
+ } else {
131
+ // On Unix systems, kill the process group
132
+ try {
133
+ process.kill(-pid, signal);
134
+ console.log(`Unix process group ${pid} killed with ${signal}`);
135
+ } catch (e) {
136
+ if (e.code !== 'ESRCH') {
137
+ // Try individual process kill as fallback
138
+ try {
139
+ process.kill(pid, signal);
140
+ console.log(`Unix process ${pid} killed with ${signal}`);
141
+ } catch (e2) {
142
+ if (e2.code !== 'ESRCH') {
143
+ console.error('Failed to kill Unix process:', e2.message);
144
+ }
145
+ }
146
+ }
147
+ }
148
+ }
149
+ } catch (e) {
150
+ if (e.code !== 'ESRCH') {
151
+ console.error('Failed to kill process tree:', e.message);
152
+ }
153
+ }
154
+ };
155
+
156
+ // Additional cleanup function to kill any remaining remotion processes
157
+ const killRemainingRemotionProcesses = async () => {
158
+ try {
159
+ if (platform() === 'win32') {
160
+ // On Windows, find and kill remotion processes
161
+ const { exec } = await import('child_process');
162
+ exec('tasklist /fi "imagename eq node.exe" /fo csv | findstr remotion', (error, stdout) => {
163
+ if (!error && stdout) {
164
+ console.log('Found remaining remotion processes, attempting cleanup...');
165
+ exec('taskkill /f /im node.exe /fi "windowtitle eq *remotion*"', (killError) => {
166
+ if (!killError) {
167
+ console.log('Cleaned up remaining remotion processes');
168
+ }
169
+ });
170
+ }
171
+ });
172
+ } else {
173
+ // On Unix systems, use pkill to find remotion processes
174
+ const { exec } = await import('child_process');
175
+ exec('pkill -f "remotion.*render"', (error) => {
176
+ if (!error) {
177
+ console.log('Cleaned up remaining remotion processes');
178
+ }
179
+ });
180
+ }
181
+ } catch (e) {
182
+ // Ignore cleanup errors
183
+ console.log('Cleanup attempt completed');
184
+ }
185
+ };
186
+
187
+ // Function to verify if process is actually terminated
188
+ const verifyProcessTerminated = async (pid) => {
189
+ try {
190
+ if (platform() === 'win32') {
191
+ const { execSync } = await import('child_process');
192
+ const result = execSync(`tasklist /fi "pid eq ${pid}"`, { encoding: 'utf8' });
193
+ return !result.includes(pid.toString());
194
+ } else {
195
+ // On Unix, sending signal 0 checks if process exists
196
+ process.kill(pid, 0);
197
+ return false; // If no error thrown, process still exists
198
+ }
199
+ } catch (e) {
200
+ // If error thrown, process doesn't exist
201
+ return true;
202
+ }
203
+ };
204
 
205
  if (controller && controller.stop) {
206
+ controller.stop = async () => {
207
  console.log('Stopping render studio cli process');
208
+ if (isProcessKilled) {
209
+ console.log('Process already terminated');
210
+ return;
211
+ }
212
+
213
+ isProcessKilled = true;
214
+
215
+ if (!childProcess.pid) {
216
+ console.log('No PID available');
217
  return;
218
  }
219
 
220
  try {
221
+ console.log(`Attempting to kill process tree with PID: ${childProcess.pid}`);
 
 
 
 
 
222
 
223
+ // Kill the entire process tree
224
+ await killProcessTree(childProcess.pid, 'SIGTERM');
225
+
226
+ // Give it a moment to terminate gracefully
227
+ setTimeout(async () => {
228
+ const isTerminated = await verifyProcessTerminated(childProcess.pid);
229
+ if (!childProcess.killed && !isTerminated) {
230
+ console.log('Process still running, trying SIGKILL');
231
+ await killProcessTree(childProcess.pid, 'SIGKILL');
232
+
233
+ // Final verification and cleanup attempt
234
+ setTimeout(async () => {
235
+ const isFinallyTerminated = await verifyProcessTerminated(childProcess.pid);
236
+ if (!isFinallyTerminated) {
237
+ console.log('Process still exists after SIGKILL, attempting additional cleanup');
238
+ await killRemainingRemotionProcesses();
239
+ } else {
240
+ console.log('Process successfully terminated');
241
  }
242
+ }, 1000);
243
+ } else {
244
+ console.log('Process terminated successfully with SIGTERM');
245
+ }
246
+ }, 2000); console.log('Process termination initiated');
247
  } catch (e) {
248
+ console.error('Failed to stop process:', e.message);
249
+ // Try cleanup as fallback
250
+ await killRemainingRemotionProcesses();
 
251
  }
252
  }
253
  }
254
 
255
  console.log('Starting video render. ' + cmd);
256
+ console.log(`Spawned process with PID: ${childProcess.pid}`);
257
+
258
+ // Store the process for cleanup
259
+ processTreePids.add(childProcess.pid);
260
+
261
  let updateCounter = 0;
262
  childProcess.stdout.on('data', (data) => {
263
  sendToObserver(jobId, data);
 
274
  });
275
 
276
  return new Promise((resolve, reject) => {
277
+ childProcess.on('close', (code, signal) => {
278
  isProcessKilled = true; // Mark process as terminated
279
+ console.log(`Render process closed with code: ${code}, signal: ${signal}`);
280
+
281
+ // Clean up tracked PIDs
282
+ processTreePids.clear();
283
+
284
  sendToObserver(jobId, code === 0 ? 'completed' : 'failed');
285
  if (code === 0) {
286
  resolve(outFile);
287
  console.log(`'${target}' completed successfully.`);
288
  } else {
289
+ const message = signal === 'SIGTERM' || signal === 'SIGKILL' ?
290
+ `'${target}' was terminated by user request.` :
291
+ `'${target}' failed with code ${code}.`;
292
+ reject({ message });
293
+ console.error(message);
294
  }
295
  });
296
 
297
  childProcess.on('error', (error) => {
298
  isProcessKilled = true; // Mark process as terminated
299
+ processTreePids.clear();
300
  console.error('Child process error:', error);
301
  sendToObserver(jobId, 'failed');
302
  reject(error);
303
  });
304
+
305
+ // Handle process exit
306
+ childProcess.on('exit', (code, signal) => {
307
+ console.log(`Render process exited with code: ${code}, signal: ${signal}`);
308
+ });
309
  });
310
  }
311