Live data from Hacker News

Fork() without exec() is dangerous in large programs

evanjones.ca

61–70 of 106 posts

Re: Fork() without exec() is dangerous in large programs

#61
post #8

Yap. Erlang 19 (latest version) switched to using a smaller spawner executable and spawns all OS processes from there by forking. So it forks something restricted and small not the whole VM. Here are the details of how it works: https://github.com/erlang/otp/blob/a5256e5221aff30f6d2cc7fab... They also claim a 3-5x speedup for launching external commands because of it. So there is a nice performance boost as well. So…

Thanks for pointing this out! Interesting that the code states that part of the concern is increasing memory usage. I don't quite understand why it would "duplicate the memory usage" since fork uses copy-on-write?

Anyway, I didn't explain it well, but this was what I was trying to get at with "fork a worker at the beginning of your program, before there can be other threads."

Re: Fork() without exec() is dangerous in large programs

#62
post #52

Earlier quoted context omitted.

> It is true that fork(2) is simple and elegant design What exactly is simple and elegant about it? Have you looked into how much tooling is necessary everywhere in unix to make it work? It's insane. It has a huge footprint and it does not provide standardized APIs to make it work for non covered cases (the best we have is pthread_atfork which is not portable).

> What exactly is simple and elegant about it? Spawn-style APIs have to take a billion parameters that are mostly set to defaults, for things like working directory, environment variables, user ID, controlling terminal. Whereas fork+exec means you can express these things in a more compositional, buildery style: fork, change the two things you actually need to change, then exec.

Meh, no one ever remembers all those stateful hidden parameters to fork() for process creation. This results in painful bugs later on, possibly affecting security. Speaking from experience.

Python says explicit is better than implicit. I agree in this case.

Re: Fork() without exec() is dangerous in large programs

#63
post #7

fork() is not per se "dangerous" in such context. You just have to be careful enough and only use asynchronous-signal-safe fonctions, as you would do in a signal handler (see https://www.securecoding.cert.org/confluence/display/c/SIG30... ) Typically calls such as malloc(), printf() etc. are strictly forbidden in the child after a fork().

This is possible, but I find these restrictions to be hard to follow. As soon as you need to call a function, you now need to audit that function to determine that it only calls other async signal safe functions. When you come back to the code to fix a bug six months later, you need to remember these restrictions.

Do this when you must, but it is easy to screw up.

Re: Fork() without exec() is dangerous in large programs

#64
post #48
post #37

Earlier quoted context omitted.

Pthreads are part of the POSIX standard. Hence the "p". Then there is pipe() and probably some more similar ones.

Then you should read what POSIX says about pthreads, regarding mandatory and optional API support, specially the implementation semantics and UB. For example, what happens to the interactions of signal handlers semantics and thread scheduling. Or what happens to the threads when the process does a fork().

> Or what happens to the threads when the process does a fork().

You mean this? http://pubs.opengroup.org/onlinepubs/009695399/functions/for...

  A process shall be created with a single thread. If a multi-threaded process
  calls fork(), the new process shall contain a replica of the calling thread
  and its entire address space, possibly including the states of mutexes and
  other resources.
Granted, it's SUSv3.

Re: Fork() without exec() is dangerous in large programs

#65
post #48
post #37

Earlier quoted context omitted.

Pthreads are part of the POSIX standard. Hence the "p". Then there is pipe() and probably some more similar ones.

Then you should read what POSIX says about pthreads, regarding mandatory and optional API support, specially the implementation semantics and UB. For example, what happens to the interactions of signal handlers semantics and thread scheduling. Or what happens to the threads when the process does a fork().

A thread belongs to a process so, for example, whatever the "second" thread does is the same as what a process does.

POSIX defines a few mechanisms for IPC.

What is optional and what is mandatory in pthreads doesn't concern me as i don't even like pthreads. (will still read what you said, later)

CSP stile concurrency over pipes (be they named or not), shared memory and synchronizations over it. POSIX defines mechanisms that work as expected over UNIX platforms.

I wonder how many (standards compliant) programs work differently between linux, *bsd, solaris, etc.

Re: Fork() without exec() is dangerous in large programs

#66

Hmm, consider we'd want to keep fork(), then the only safe way to deal with this would be, that critical sections were actually transactions implemented on the OS level and at after fork() in the child all transactions in flight get rolled back before returning from fork(). I see two implementation challenges with that suggestion: 1.) implementing that transaction mechanism as a kernel feature: When entering a CS mar…

Sounds like you are trying to re-invent Software Transactional Memory - was a nice idea 10 years ago, but after lots of research it is still not available in common runtimes...

Some more problems to consider:

1. how do you roll back IO that happens in a critical section? 2. your global semaphore will be highly contended and basically destroy scalability

Re: Fork() without exec() is dangerous in large programs

#67
post #2

In my opinion, it's libraries that secretly use threads under the covers (but still access state shared with the rest of the program like the malloc heap) that are what's dangerous.

Libraries should not be disallowed from spawning threads. Threads are inherently the right way to do concurrency for more complex tasks. fork() is a tremendously complex call and has huge implications on all things in the OS. For many years now I have the rule that new code must not use fork() and I am living a happier life. It's a bad call and it has no use if you have good threading support in the language. There a…

I would add, in many/most cases using fork() without calling exec() just doesn't make sense. Even at the system call level you have clone(), which is how actual threading is implemented under the covers. And really, if you need a thread, just get a thread. I see extremely few or no advantages to doing fork() instead of spawning a new thread normally using whatever interface is provided (except in the case of exec() where fork() or vfork() is necessary).

Re: Fork() without exec() is dangerous in large programs

#68
post #50

> How to use fork safely: 1. Only use fork to immediately call exec. 2. Fork a worker at the beginning of your program, before there can be other threads. 3. Only use fork in toy programs. 4. Stop writing broken multithreaded code altogether or if you must at least run an event loop per core/thread and use a wrapper for fork() to put the system into a fork()able state before forking. It's nice and reliable. Multithre…

Fork/Exec is only good if you are going to run a different program then you are currently executing. If you want a series of worker threads that are effectively the same as the master/initial thread. The proper way to do this clone(2) not fork, or fork/exec. When used properly clone allows your group of threads to share a single PID, and TGID (Thread Group IDentifier). This cuts down on kernel resources your process(…

$ man 2 clone

man: No entry for clone in the manual.

Re: Fork() without exec() is dangerous in large programs

#69
post #51

Earlier quoted context omitted.

compilers and runtimes can spawn threads implicitly when required (think of openmp, cilk+, or even autopar). Are you saying that libraries shouldn't use these facilities?

If you're spawning threads implicitly then you're no longer a library, you're a framework. Sometimes a framework is a good fit, but frameworks don't compose (if you try to use two frameworks in the same application you're gonna have a bad time) so there is a cost. But in any case, better a framework that's explicitly a framework than a "library" which can't actually be used freely in general-purpose code.

would you consider a parallelized BLAS a framework?

Re: Fork() without exec() is dangerous in large programs

#70
post #61
post #8

Yap. Erlang 19 (latest version) switched to using a smaller spawner executable and spawns all OS processes from there by forking. So it forks something restricted and small not the whole VM. Here are the details of how it works: https://github.com/erlang/otp/blob/a5256e5221aff30f6d2cc7fab... They also claim a 3-5x speedup for launching external commands because of it. So there is a nice performance boost as well. So…

Thanks for pointing this out! Interesting that the code states that part of the concern is increasing memory usage. I don't quite understand why it would "duplicate the memory usage" since fork uses copy-on-write? Anyway, I didn't explain it well, but this was what I was trying to get at with "fork a worker at the beginning of your program, before there can be other threads."

Copy-on-write doesn't work the same on every OS... Linux uses over-commit so it's not a big deal, but Solaris (and NT) don't over-commit so you actually need enough VM at the time when you call fork or fork will fail, so you may need to provide a lot of swap space on those systems to successfully call fork in large processes.
Post reply on HN