PEBL 2.2
Psychology Experiment Building Language - Cross-platform psychological experiment development system
ExperimentRunner.cpp
Go to the documentation of this file.
1// ExperimentRunner.cpp - Process management implementation
2// Copyright (c) 2026 Shane T. Mueller
3// Licensed under GPL
4
5#include "ExperimentRunner.h"
6#include "LauncherConfig.h"
7#include <cstdio>
8#include <cstdlib>
9#include <fstream>
10#include <sstream>
11#include <sys/stat.h>
12
13#ifdef _WIN32
14#include <windows.h>
15#include <shlobj.h>
16#else
17#include <unistd.h>
18#include <sys/wait.h>
19#include <signal.h>
20#include <pwd.h>
21#include <fcntl.h>
22#endif
23
24#ifdef _WIN32
25// Convert forward slashes to backslashes for Windows API compatibility
26static std::string NormalizeWindowsPath(const std::string& path) {
27 std::string result = path;
28 for (char& c : result) {
29 if (c == '/') c = '\\';
30 }
31 return result;
32}
33#endif
34
36 : mConfig(nullptr)
37 , mIsRunning(false)
38 , mLaunchTime(0)
39 , mCurrentFullscreen(false)
40 , mExitCode(-1)
41 , mProcessId(0)
42{
43#ifdef _WIN32
44 mProcessHandle = nullptr;
45 mStdoutReadPipe = nullptr;
46 mStdoutWritePipe = nullptr;
47 mStderrReadPipe = nullptr;
48 mStderrWritePipe = nullptr;
49#else
50 mStdoutPipe[0] = -1;
51 mStdoutPipe[1] = -1;
52 mStderrPipe[0] = -1;
53 mStderrPipe[1] = -1;
54#endif
55}
56
58 : mConfig(config)
59 , mIsRunning(false)
60 , mLaunchTime(0)
61 , mCurrentFullscreen(false)
62 , mExitCode(-1)
63 , mProcessId(0)
64{
65#ifdef _WIN32
66 mProcessHandle = nullptr;
67 mStdoutReadPipe = nullptr;
68 mStdoutWritePipe = nullptr;
69 mStderrReadPipe = nullptr;
70 mStderrWritePipe = nullptr;
71#else
72 mStdoutPipe[0] = -1;
73 mStdoutPipe[1] = -1;
74 mStderrPipe[0] = -1;
75 mStderrPipe[1] = -1;
76#endif
77}
78
80{
81 if (mIsRunning) {
82 Terminate();
83 }
84}
85
86std::string ExperimentRunner::GetPEBLExecutablePath() const
87{
88 // Check config first if available
89 if (mConfig) {
90 std::string configPath = mConfig->GetPeblExecutablePath();
91 if (!configPath.empty()) {
92 return configPath;
93 }
94 }
95
96 // Fall back to auto-detection
97#ifdef _WIN32
98 // Windows: Look for pebl2.exe in same directory as launcher or in PATH
99 char exePath[MAX_PATH];
100 GetModuleFileNameA(NULL, exePath, MAX_PATH);
101
102 // Replace launcher.exe with pebl2.exe
103 std::string path(exePath);
104 size_t lastSlash = path.find_last_of("\\/");
105 if (lastSlash != std::string::npos) {
106 path = path.substr(0, lastSlash + 1) + "pebl2.exe";
107 } else {
108 path = "pebl2.exe";
109 }
110 return path;
111#else
112 // Linux/macOS: Try to find pebl2 in same directory as launcher
113 char exePath[1024];
114 ssize_t len = readlink("/proc/self/exe", exePath, sizeof(exePath) - 1);
115
116 if (len != -1) {
117 exePath[len] = '\0';
118 std::string path(exePath);
119 size_t lastSlash = path.find_last_of('/');
120 if (lastSlash != std::string::npos) {
121 path = path.substr(0, lastSlash + 1) + "pebl2";
122 return path;
123 }
124 }
125
126 // Fallback: assume pebl2 is in PATH
127 return "pebl2";
128#endif
129}
130
131bool ExperimentRunner::RunExperiment(const std::string& scriptPath,
132 const std::vector<std::string>& args,
133 const std::string& subjectCode,
134 const std::string& language,
135 bool fullscreen)
136{
137 if (mIsRunning) {
138 printf("Warning: Experiment already running\n");
139 return false;
140 }
141
142 std::string peblPath = GetPEBLExecutablePath();
143
144 // Extract directory from script path
145 std::string workingDir;
146 size_t lastSlash = scriptPath.find_last_of("/\\");
147 if (lastSlash != std::string::npos) {
148 workingDir = scriptPath.substr(0, lastSlash);
149 } else {
150 workingDir = ".";
151 }
152
153 // Get just the filename for passing to PEBL
154 std::string scriptFilename;
155 if (lastSlash != std::string::npos) {
156 scriptFilename = scriptPath.substr(lastSlash + 1);
157 } else {
158 scriptFilename = scriptPath;
159 }
160
161 // Build complete argument list with PEBL command-line flags
162 std::vector<std::string> fullArgs;
163
164 // Add user-provided args first (these may include -v for positional params)
165 for (const auto& arg : args) {
166 fullArgs.push_back(arg);
167 }
168
169 // Add subject code with -s flag
170 if (!subjectCode.empty()) {
171 fullArgs.push_back("-s");
172 fullArgs.push_back(subjectCode);
173 }
174
175 // Add language if specified
176 if (!language.empty()) {
177 fullArgs.push_back("--language");
178 fullArgs.push_back(language);
179 }
180
181 // Add fullscreen flag
182 if (fullscreen) {
183 fullArgs.push_back("--fullscreen");
184 }
185
186#ifdef _WIN32
187 // Windows: Use CreateProcess with pipes for stdout/stderr capture
188 // Normalize paths - convert forward slashes to backslashes for Windows API
189 std::string winPeblPath = NormalizeWindowsPath(peblPath);
190 std::string winWorkingDir = NormalizeWindowsPath(workingDir);
191
192 std::string cmdLine = "\"" + winPeblPath + "\" \"" + scriptFilename + "\"";
193
194 // Add all arguments
195 for (const auto& arg : fullArgs) {
196 cmdLine += " ";
197 if (arg.find(' ') != std::string::npos) {
198 cmdLine += "\"" + arg + "\"";
199 } else {
200 cmdLine += arg;
201 }
202 }
203
204 printf("Windows CreateProcess: cmd=%s, workdir=%s\n", cmdLine.c_str(), winWorkingDir.c_str());
205
206 // Clear output buffers
207 mStdoutBuffer.clear();
208 mStderrBuffer.clear();
209
210 // Set up security attributes for pipe inheritance
211 SECURITY_ATTRIBUTES sa;
212 sa.nLength = sizeof(SECURITY_ATTRIBUTES);
213 sa.bInheritHandle = TRUE; // Child process inherits handles
214 sa.lpSecurityDescriptor = NULL;
215
216 // Create stdout pipe
217 HANDLE hStdoutRead, hStdoutWrite;
218 if (!CreatePipe(&hStdoutRead, &hStdoutWrite, &sa, 0)) {
219 printf("Failed to create stdout pipe: %lu\n", GetLastError());
220 return false;
221 }
222 // Ensure read end is not inherited by child
223 SetHandleInformation(hStdoutRead, HANDLE_FLAG_INHERIT, 0);
224
225 // Create stderr pipe
226 HANDLE hStderrRead, hStderrWrite;
227 if (!CreatePipe(&hStderrRead, &hStderrWrite, &sa, 0)) {
228 printf("Failed to create stderr pipe: %lu\n", GetLastError());
229 CloseHandle(hStdoutRead);
230 CloseHandle(hStdoutWrite);
231 return false;
232 }
233 // Ensure read end is not inherited by child
234 SetHandleInformation(hStderrRead, HANDLE_FLAG_INHERIT, 0);
235
236 // Store pipe handles
237 mStdoutReadPipe = hStdoutRead;
238 mStdoutWritePipe = hStdoutWrite;
239 mStderrReadPipe = hStderrRead;
240 mStderrWritePipe = hStderrWrite;
241
242 STARTUPINFOA si;
243 PROCESS_INFORMATION pi;
244 ZeroMemory(&si, sizeof(si));
245 si.cb = sizeof(si);
246 si.hStdOutput = hStdoutWrite;
247 si.hStdError = hStderrWrite;
248 si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
249 si.dwFlags |= STARTF_USESTDHANDLES;
250 ZeroMemory(&pi, sizeof(pi));
251
252 // Create the child process with working directory set
253 if (!CreateProcessA(
254 NULL, // Application name
255 const_cast<char*>(cmdLine.c_str()), // Command line
256 NULL, // Process security attributes
257 NULL, // Thread security attributes
258 TRUE, // Inherit handles (must be TRUE for pipes)
259 0, // Creation flags
260 NULL, // Environment
261 winWorkingDir.c_str(), // Current directory (IMPORTANT!) - must use backslashes
262 &si, // Startup info
263 &pi)) // Process info
264 {
265 printf("Failed to create process: %s\n", cmdLine.c_str());
266 printf("Working directory was: %s\n", winWorkingDir.c_str());
267 printf("GetLastError: %lu\n", GetLastError());
268 // Clean up pipe handles
269 CloseHandle(hStdoutRead);
270 CloseHandle(hStdoutWrite);
271 CloseHandle(hStderrRead);
272 CloseHandle(hStderrWrite);
273 mStdoutReadPipe = nullptr;
274 mStdoutWritePipe = nullptr;
275 mStderrReadPipe = nullptr;
276 mStderrWritePipe = nullptr;
277 return false;
278 }
279
280 mProcessHandle = pi.hProcess;
281 mProcessId = pi.dwProcessId;
282 CloseHandle(pi.hThread);
283
284 // Close write ends in parent - child has copies
285 CloseHandle(hStdoutWrite);
286 CloseHandle(hStderrWrite);
287 mStdoutWritePipe = nullptr;
288 mStderrWritePipe = nullptr;
289
290 mIsRunning = true;
291
292#else
293 // Unix: Create pipes for stdout/stderr capture
294 if (pipe(mStdoutPipe) < 0 || pipe(mStderrPipe) < 0) {
295 printf("Failed to create pipes\n");
296 return false;
297 }
298
299 // Make read ends non-blocking
300 fcntl(mStdoutPipe[0], F_SETFL, O_NONBLOCK);
301 fcntl(mStderrPipe[0], F_SETFL, O_NONBLOCK);
302
303 // Clear output buffers
304 mStdoutBuffer.clear();
305 mStderrBuffer.clear();
306
307 pid_t pid = fork();
308
309 if (pid < 0) {
310 printf("Failed to fork process\n");
311 close(mStdoutPipe[0]);
312 close(mStdoutPipe[1]);
313 close(mStderrPipe[0]);
314 close(mStderrPipe[1]);
315 return false;
316 }
317
318 if (pid == 0) {
319 // Child process - redirect stdout/stderr to pipes
320 close(mStdoutPipe[0]); // Close read ends
321 close(mStderrPipe[0]);
322
323 dup2(mStdoutPipe[1], STDOUT_FILENO);
324 dup2(mStderrPipe[1], STDERR_FILENO);
325
326 close(mStdoutPipe[1]);
327 close(mStderrPipe[1]);
328
329 // Change to working directory
330 if (chdir(workingDir.c_str()) != 0) {
331 fprintf(stderr, "Failed to change directory to: %s\n", workingDir.c_str());
332 exit(1);
333 }
334
335 std::vector<char*> argv;
336 argv.push_back(const_cast<char*>(peblPath.c_str()));
337 argv.push_back(const_cast<char*>(scriptFilename.c_str()));
338
339 for (const auto& arg : fullArgs) {
340 argv.push_back(const_cast<char*>(arg.c_str()));
341 }
342
343 argv.push_back(nullptr);
344
345 execvp(peblPath.c_str(), argv.data());
346
347 // If we get here, exec failed
348 fprintf(stderr, "Failed to execute: %s\n", peblPath.c_str());
349 exit(1);
350 }
351
352 // Parent process - close write ends
353 close(mStdoutPipe[1]);
354 close(mStderrPipe[1]);
355 mStdoutPipe[1] = -1;
356 mStderrPipe[1] = -1;
357
358 mProcessId = pid;
359 mIsRunning = true;
360#endif
361
362 printf("Launched experiment: %s (PID: %lu) in directory: %s\n",
363 scriptFilename.c_str(), (unsigned long)mProcessId, workingDir.c_str());
364
365 // Log the launch
366 mCurrentScript = scriptPath;
367 mCurrentSubject = subjectCode;
368 mCurrentLanguage = language;
369 mCurrentFullscreen = fullscreen;
370 mLaunchTime = std::time(nullptr);
371 LogLaunch(scriptPath, subjectCode, language, fullscreen);
372
373 return true;
374}
375
377{
378 if (!mIsRunning) {
379 return -1;
380 }
381
382#ifdef _WIN32
383 WaitForSingleObject(mProcessHandle, INFINITE);
384
385 // Do final read of any remaining output
386 UpdateOutput();
387
388 // Close pipe read handles
389 if (mStdoutReadPipe) {
390 CloseHandle(static_cast<HANDLE>(mStdoutReadPipe));
391 mStdoutReadPipe = nullptr;
392 }
393 if (mStderrReadPipe) {
394 CloseHandle(static_cast<HANDLE>(mStderrReadPipe));
395 mStderrReadPipe = nullptr;
396 }
397
398 DWORD exitCode;
399 GetExitCodeProcess(mProcessHandle, &exitCode);
400 mExitCode = static_cast<int>(exitCode);
401 CloseHandle(mProcessHandle);
402
403 mProcessHandle = nullptr;
404 mIsRunning = false;
405
406 LogCompletion(mExitCode);
407 return mExitCode;
408#else
409 int status;
410 waitpid(mProcessId, &status, 0);
411
412 // Do final read of any remaining output
413 UpdateOutput();
414
415 // Close pipes
416 if (mStdoutPipe[0] >= 0) {
417 close(mStdoutPipe[0]);
418 mStdoutPipe[0] = -1;
419 }
420 if (mStderrPipe[0] >= 0) {
421 close(mStderrPipe[0]);
422 mStderrPipe[0] = -1;
423 }
424
425 mIsRunning = false;
426
427 mExitCode = -1;
428 if (WIFEXITED(status)) {
429 mExitCode = WEXITSTATUS(status);
430 }
431
432 LogCompletion(mExitCode);
433 return mExitCode;
434#endif
435}
436
438{
439 if (!mIsRunning) {
440 return;
441 }
442
443#ifdef _WIN32
444 TerminateProcess(mProcessHandle, 1);
445 CloseHandle(mProcessHandle);
446 mProcessHandle = nullptr;
447 // Close pipe handles
448 if (mStdoutReadPipe) {
449 CloseHandle(static_cast<HANDLE>(mStdoutReadPipe));
450 mStdoutReadPipe = nullptr;
451 }
452 if (mStderrReadPipe) {
453 CloseHandle(static_cast<HANDLE>(mStderrReadPipe));
454 mStderrReadPipe = nullptr;
455 }
456#else
457 kill(mProcessId, SIGTERM);
458
459 // Close pipes
460 if (mStdoutPipe[0] >= 0) {
461 close(mStdoutPipe[0]);
462 mStdoutPipe[0] = -1;
463 }
464 if (mStderrPipe[0] >= 0) {
465 close(mStderrPipe[0]);
466 mStderrPipe[0] = -1;
467 }
468#endif
469
470 mIsRunning = false;
471 LogCompletion(-999); // Log termination with special exit code
472 printf("Terminated experiment (PID: %lu)\n", mProcessId);
473}
474
476{
477 std::string logPath;
478
479#ifdef _WIN32
480 // Windows: Documents folder
481 char docPath[MAX_PATH];
482 if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_PERSONAL, NULL, 0, docPath))) {
483 logPath = std::string(docPath);
484 }
485#else
486 // Linux/macOS: ~/Documents or ~ if Documents doesn't exist
487 const char* homeDir = getenv("HOME");
488 if (!homeDir) {
489 struct passwd* pw = getpwuid(getuid());
490 homeDir = pw->pw_dir;
491 }
492
493 std::string docDir = std::string(homeDir) + "/Documents";
494 struct stat st;
495 if (stat(docDir.c_str(), &st) == 0 && S_ISDIR(st.st_mode)) {
496 logPath = docDir;
497 } else {
498 logPath = homeDir;
499 }
500#endif
501
502 // Look for pebl-exp.X.X directories (newest first)
503 const char* versions[] = {
504 "pebl-exp.2.4",
505 "pebl-exp.2.3",
506 "pebl-exp.2.2",
507 "pebl-exp.2.1",
508 "pebl-exp.2.0",
509 "pebl-exp.0.14",
510 "pebl-exp"
511 };
512
513#ifdef _WIN32
514 const char* separator = "\\";
515#else
516 const char* separator = "/";
517#endif
518
519 for (const char* version : versions) {
520 std::string peblPath = logPath + separator + version;
521 struct stat st;
522 if (stat(peblPath.c_str(), &st) == 0 && S_ISDIR(st.st_mode)) {
523 // Check if logs subdirectory exists, create if needed
524 std::string logsPath = peblPath + separator + "logs";
525 struct stat logsSt;
526 if (stat(logsPath.c_str(), &logsSt) != 0 || !S_ISDIR(logsSt.st_mode)) {
527 // Create logs directory
528#ifdef _WIN32
529 CreateDirectoryA(logsPath.c_str(), NULL);
530#else
531 mkdir(logsPath.c_str(), 0755);
532#endif
533 }
534 return logsPath + separator + "launcher-log.csv";
535 }
536 }
537
538 // Fallback to home directory
539 return logPath + separator + "launcher-log.csv";
540}
541
542void ExperimentRunner::LogLaunch(const std::string& scriptPath,
543 const std::string& subjectCode,
544 const std::string& language,
545 bool fullscreen)
546{
547 std::string logPath = GetLaunchLogPath();
548 bool fileExists = false;
549
550 // Check if file already exists
551 struct stat st;
552 if (stat(logPath.c_str(), &st) == 0) {
553 fileExists = true;
554 }
555
556 std::ofstream logFile(logPath, std::ios::app);
557 if (!logFile.is_open()) {
558 printf("Warning: Could not open launch log file: %s\n", logPath.c_str());
559 return;
560 }
561
562 // Write header if new file
563 if (!fileExists) {
564 logFile << "timestamp,experiment_path,subject_code,language,fullscreen,exit_status,duration_seconds" << std::endl;
565 }
566
567 // Write launch record (completion will add exit_status and duration)
568 char timeBuf[64];
569 struct tm* timeinfo = std::localtime(&mLaunchTime);
570 std::strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", timeinfo);
571
572 logFile << timeBuf << ","
573 << "\"" << scriptPath << "\","
574 << "\"" << subjectCode << "\","
575 << "\"" << language << "\","
576 << (fullscreen ? "true" : "false") << ","
577 << "RUNNING,"
578 << "0" << std::endl;
579
580 logFile.close();
581}
582
583void ExperimentRunner::LogCompletion(int exitStatus)
584{
585 std::string logPath = GetLaunchLogPath();
586
587 // Calculate duration
588 std::time_t endTime = std::time(nullptr);
589 int duration = static_cast<int>(endTime - mLaunchTime);
590
591 // Read the entire log file
592 std::ifstream inFile(logPath);
593 if (!inFile.is_open()) {
594 return;
595 }
596
597 std::stringstream buffer;
598 std::string line;
599 std::string lastLine;
600 bool foundRunning = false;
601
602 // Read all lines and update the last RUNNING entry
603 while (std::getline(inFile, line)) {
604 if (line.find("RUNNING") != std::string::npos && !foundRunning) {
605 // This is the running entry we want to update
606 foundRunning = true;
607
608 // Replace RUNNING with exit status and 0 with duration
609 size_t pos = line.find("RUNNING");
610 if (pos != std::string::npos) {
611 line.replace(pos, 7, std::to_string(exitStatus));
612 }
613
614 // Replace the last 0 (duration) with actual duration
615 pos = line.rfind(",0");
616 if (pos != std::string::npos) {
617 line = line.substr(0, pos + 1) + std::to_string(duration);
618 }
619 }
620 buffer << line << std::endl;
621 }
622 inFile.close();
623
624 // Write back the updated log
625 std::ofstream outFile(logPath);
626 if (outFile.is_open()) {
627 outFile << buffer.str();
628 outFile.close();
629 }
630}
631
633{
634#ifdef _WIN32
635 // Read from stdout pipe (non-blocking using PeekNamedPipe)
636 if (mStdoutReadPipe) {
637 HANDLE hPipe = static_cast<HANDLE>(mStdoutReadPipe);
638 DWORD bytesAvailable = 0;
639 while (PeekNamedPipe(hPipe, NULL, 0, NULL, &bytesAvailable, NULL) && bytesAvailable > 0) {
640 char buffer[4096];
641 DWORD bytesToRead = (bytesAvailable < sizeof(buffer) - 1) ? bytesAvailable : sizeof(buffer) - 1;
642 DWORD bytesRead = 0;
643 if (ReadFile(hPipe, buffer, bytesToRead, &bytesRead, NULL) && bytesRead > 0) {
644 buffer[bytesRead] = '\0';
645 mStdoutBuffer.append(buffer);
646 } else {
647 break;
648 }
649 }
650 }
651
652 // Read from stderr pipe (non-blocking using PeekNamedPipe)
653 if (mStderrReadPipe) {
654 HANDLE hPipe = static_cast<HANDLE>(mStderrReadPipe);
655 DWORD bytesAvailable = 0;
656 while (PeekNamedPipe(hPipe, NULL, 0, NULL, &bytesAvailable, NULL) && bytesAvailable > 0) {
657 char buffer[4096];
658 DWORD bytesToRead = (bytesAvailable < sizeof(buffer) - 1) ? bytesAvailable : sizeof(buffer) - 1;
659 DWORD bytesRead = 0;
660 if (ReadFile(hPipe, buffer, bytesToRead, &bytesRead, NULL) && bytesRead > 0) {
661 buffer[bytesRead] = '\0';
662 mStderrBuffer.append(buffer);
663 } else {
664 break;
665 }
666 }
667 }
668#else
669 // Read from stdout pipe (non-blocking)
670 if (mStdoutPipe[0] >= 0) {
671 char buffer[4096];
672 ssize_t bytesRead;
673 while ((bytesRead = read(mStdoutPipe[0], buffer, sizeof(buffer) - 1)) > 0) {
674 buffer[bytesRead] = '\0';
675 mStdoutBuffer.append(buffer);
676 }
677 }
678
679 // Read from stderr pipe (non-blocking)
680 if (mStderrPipe[0] >= 0) {
681 char buffer[4096];
682 ssize_t bytesRead;
683 while ((bytesRead = read(mStderrPipe[0], buffer, sizeof(buffer) - 1)) > 0) {
684 buffer[bytesRead] = '\0';
685 mStderrBuffer.append(buffer);
686 }
687 }
688#endif
689}
690
692{
693 if (!mIsRunning) {
694 return false;
695 }
696
697#ifdef _WIN32
698 // Windows: Check if process handle is still valid and running
699 if (!mProcessHandle) {
700 mIsRunning = false;
701 return false;
702 }
703
704 // Read any available output while process is running
705 UpdateOutput();
706
707 DWORD exitCode;
708 if (GetExitCodeProcess(mProcessHandle, &exitCode)) {
709 if (exitCode == STILL_ACTIVE) {
710 return true;
711 } else {
712 // Process has finished
713 mExitCode = static_cast<int>(exitCode);
714 printf("Process %lu finished with exit code %d\n", mProcessId, mExitCode);
715 fflush(stdout);
716 // Also log to file for debugging
717 FILE* f = fopen("chain_debug.log", "a");
718 if (f) {
719 fprintf(f, "ExperimentRunner: Process finished, exit code = %d\n", mExitCode);
720 fclose(f);
721 }
722
723 // Do final read of any remaining output
724 UpdateOutput();
725
726 // Close pipe handles
727 if (mStdoutReadPipe) {
728 CloseHandle(static_cast<HANDLE>(mStdoutReadPipe));
729 mStdoutReadPipe = nullptr;
730 }
731 if (mStderrReadPipe) {
732 CloseHandle(static_cast<HANDLE>(mStderrReadPipe));
733 mStderrReadPipe = nullptr;
734 }
735
736 CloseHandle(mProcessHandle);
737 mProcessHandle = nullptr;
738 mIsRunning = false;
739 LogCompletion(mExitCode);
740 return false;
741 }
742 } else {
743 // Error querying process - assume it's dead
744 // Close pipe handles
745 if (mStdoutReadPipe) {
746 CloseHandle(static_cast<HANDLE>(mStdoutReadPipe));
747 mStdoutReadPipe = nullptr;
748 }
749 if (mStderrReadPipe) {
750 CloseHandle(static_cast<HANDLE>(mStderrReadPipe));
751 mStderrReadPipe = nullptr;
752 }
753 CloseHandle(mProcessHandle);
754 mProcessHandle = nullptr;
755 mIsRunning = false;
756 return false;
757 }
758#else
759 // Unix: Use waitpid with WNOHANG to check without blocking
760 // This is exactly what PEBL's CheckProcessStatus() does
761 int status;
762 pid_t result = waitpid(mProcessId, &status, WNOHANG);
763
764 if (result == 0) {
765 // Process still running
766 return true;
767 }
768 else if (result == mProcessId) {
769 // Process has finished
770 printf("Process %d finished\n", mProcessId);
771
772 // Do final read of any remaining output
773 UpdateOutput();
774
775 // Close pipes
776 if (mStdoutPipe[0] >= 0) {
777 close(mStdoutPipe[0]);
778 mStdoutPipe[0] = -1;
779 }
780 if (mStderrPipe[0] >= 0) {
781 close(mStderrPipe[0]);
782 mStderrPipe[0] = -1;
783 }
784
785 mIsRunning = false;
786
787 mExitCode = -1;
788 if (WIFEXITED(status)) {
789 mExitCode = WEXITSTATUS(status);
790 printf(" Exit code: %d\n", mExitCode);
791 } else if (WIFSIGNALED(status)) {
792 printf(" Terminated by signal %d\n", WTERMSIG(status));
793 }
794
795 LogCompletion(mExitCode);
796 return false;
797 }
798 else {
799 // Error or already reaped (result == -1)
800 printf("waitpid error for PID %d (already reaped or error)\n", mProcessId);
801 mIsRunning = false;
802 return false;
803 }
804#endif
805}
#define NULL
Definition BinReloc.cpp:317
bool RunExperiment(const std::string &scriptPath, const std::vector< std::string > &args, const std::string &subjectCode="", const std::string &language="", bool fullscreen=false)
static std::string GetLaunchLogPath()
std::string GetPeblExecutablePath() const