Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Forking a child process during a test hangs and prints error message. #383

Closed
CamJN opened this issue Aug 24, 2021 · 7 comments
Closed

Forking a child process during a test hangs and prints error message. #383

CamJN opened this issue Aug 24, 2021 · 7 comments

Comments

@CamJN
Copy link

CamJN commented Aug 24, 2021

I am working on some code that deals with processes, In order to test one function, I need to know the PID of a running process that I own, with more than 1 argument. I tried to fork then exec a child process for this purpose, but the 3 processes then hang (criterion parent/runner process, test process, and my child process), and the following message gets printed:
[ERR ] Received message identified by a PID '21459' that is not a child process.

I think there's some kind of magic going around with how children of criterion's runner process are handled, but I need to know how to opt out of that, or perhaps it should disable itself after one fork.

@CamJN
Copy link
Author

CamJN commented Aug 24, 2021

reduced example:

#include <criterion/criterion.h>
#include <unistd.h>

pid_t spawn(const char *executable, ...);

Test(test,spawn){
  int status;
  waitpid(spawn("/bin/sleep","sleep","2"),&status,0);
}

pid_t spawn(const char *executable, ...) {
  va_list val;
  const char** args = NULL;
  int argc = 1;//room for terminating NULL
  pid_t pid = fork();
  if (pid != 0) return pid;

  // Determine number of variadic arguments
  va_start(val, executable);
  while (va_arg(val, const char *) != NULL) argc++;
  va_end(val);
  cr_log_info("%d\n",argc);
  // Allocate args, put references to command / variadic arguments + NULL in args
  // because last val is NULL, and we malloc'd an extra pointer, we're good
  args = (const char **) malloc(argc * sizeof(char*));
  va_start(val, executable);
  for (int i = 0; i < argc && args[i] != NULL; i++) {
    args[i] = va_arg(val, const char *);
    cr_log_info("%s\n",args[i]);
  }
  va_end(val);

  execv(executable, args);
  _Exit(EXIT_FAILURE);
}

@Snaipe
Copy link
Owner

Snaipe commented Nov 10, 2021

The issue is that criterion is receiving a message from your forked process via cr_log_info, but has no idea who it is, since it's a process outside of its watch list. I would expect that moving the fork after all of your argv preparation would cause the test to stop hanging.

I'm not sure if this is something that we want so support. The runner certainly has no way to correlate a message from a random PID, so maybe the most "unsurprising" behaviour there would be to record somewhere the test PID and access it in these children. Not great though.

@CamJN
Copy link
Author

CamJN commented Nov 28, 2021

Ok so moving the execv fixed the hang. However I then encountered a new issue. The child process' arguments were somehow swapped out from what I passed to execv and were replaced with boxfort-worker.

@MrAnno
Copy link
Collaborator

MrAnno commented Nov 28, 2021

@CamJN Your example code contained some undefined behavior. After fixing those, it started to work correctly for me:

#include <criterion/criterion.h>
#include <unistd.h>

pid_t spawn(const char *executable, ...);

Test(test,spawn){
  int status;
  waitpid(spawn("/bin/sleep", "sleep", "2", NULL), &status, 0);
}

pid_t spawn(const char *executable, ...) {
  va_list val;
  const char** args = NULL;
  int argc = 1; //room for terminating NULL

  va_start(val, executable);
  while (va_arg(val, const char *) != NULL) argc++;
  va_end(val);
  cr_log_info("%d\n",argc);

  args = (const char **) malloc(argc * sizeof(char*));
  va_start(val, executable);
  for (int i = 0; i < argc; i++) {
    args[i] = va_arg(val, const char *);
    cr_log_info("%s\n", args[i]);
  }
  va_end(val);

  pid_t pid = fork();
  if (pid != 0) return pid;
  execv(executable, args);
  _Exit(EXIT_FAILURE);
}

Note: Criterion tests are executed in a sandboxed environment. These are called boxfort-workers, so your spawned process' parent will definitely be boxfort-worker.

@CamJN
Copy link
Author

CamJN commented Nov 29, 2021

@MrAnno I understand that the parent process is boxfort-worker, the surprising thing is that the child process doesn't have its process name and args set properly by execv. And since the whole purpose of spawning a child process is to have one whose args I can control for testing purposes, that's a problem.

@MrAnno
Copy link
Collaborator

MrAnno commented Nov 29, 2021

In the above example, sleep is executed with its arguments set properly. Is that not the case in your environment?

@CamJN
Copy link
Author

CamJN commented Nov 29, 2021

Ugh nevermind, looks like it was a race, the execv hadn't finished setup by the time i checked the args.

@CamJN CamJN closed this as completed Nov 29, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants