Live data from Hacker News

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

evanjones.ca

91–100 of 106 posts

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

#91
post #87

Earlier quoted context omitted.

I don't think this makes the case for elegance at all. Global vars set wherever in a program are preferable to explicit params when creating a new process? And there's no way to have default params or anything?

> Global vars set wherever in a program are preferable to explicit params when creating a new process? The outside world is always going to be implicit global mutable state; spawning an arbitrary process to do who-knows-what is inherently that kind of problem. If you could express it as an actual function you wouldn't need to run a separate process. > And there's no way to have default params or anything? Well not in…

Well by that reasoning there's never any need for function purity since you can't eliminate global state on current computer designs.

C easily allows you to have multiple functions. exec_with_current_state or something. And a struct is a way, too.

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

#92

Earlier quoted context omitted.

...And why would you want that? The advantage for fork(2) over clone is that you CAN'T share those datastructures, making it harder to write code resulting a deadlock: you have to share explicitely. As for the kernel resources for the process, you'd be surprised how large RAM has gotten these days...

...And why would you want that? All your applications share the same virtual memory space. So you have a much higher hit rate in your TLB, then without. It greatly lowers the chance that shared memory will leave L3 cache. Cache hit rates are very important... The advantage for fork(2) over clone is that you CAN'T share those datastructures, making it harder to write code resulting a deadlock Dead locking has nothing…

Cache hit rates aren't as important as you seem to think. If you're not google, and your app doesn't have hard/soft realtime constraints (videogames, flight control systems, etc.) your app will probably be fast enough. You'd be surprised how fast computers are nowadays...

  >If you are locking memory/data structure 
  locations you are likely doing something wrong. 
Oh, concurrent memory fences! Well, that solves all my problems. Let's see. All I have to do is give up all hope of my code ever being portable to mutiple architectures, and than dig around in asm to add support for them to my environment, which can range from mildly annoying (C), to near-impossible (JVM). Or I can use fork(2), and do what programmers have been doing for decades: trading performance for simplicity, and productivity.

That'll be a really hard decision.

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

#93
post #10

Earlier quoted context omitted.

I completely disagree. The only part of this that seems dangerous is the fact that it can turn a single-threaded program into a multi-threaded one, and the only real problem there is it makes fork-without-exec unsafe. But fork-without-exec is basically unsafe anyway unless you can absolutely guarantee that nothing has ever spawned a thread. Meanwhile, preventing libraries from using threading under the covers basical…

This is the very first time I have heard about fork-without-exec as dangerous, but it is not the first time I have heard about having to be careful about libraries and hidden threads. The thing is that any thread you launch is a big piece of machinery and your attempt to hide it is inevitably going to be a leaky abstraction.

> The thing is that any thread you launch is a big piece of machinery and your attempt to hide it is inevitably going to be a leaky abstraction.

That's not true at all. Threads aren't that complicated, and there's lots of ways of using them that aren't complicated and don't leak anything. As a trivial example, if I want to process a bunch of data, it might be faster to process it in parallel, and I can use a thread to do that without causing any observable difference in behavior (beyond potentially turning a single-threaded program into a multithreaded one). And if I'm on a platform like OS X or iOS, I can use higher-level threading APIs like libdispatch that make it even simpler.

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

#94

Earlier quoted context omitted.

...And why would you want that? All your applications share the same virtual memory space. So you have a much higher hit rate in your TLB, then without. It greatly lowers the chance that shared memory will leave L3 cache. Cache hit rates are very important... The advantage for fork(2) over clone is that you CAN'T share those datastructures, making it harder to write code resulting a deadlock Dead locking has nothing…

Cache hit rates aren't as important as you seem to think. If you're not google, and your app doesn't have hard/soft realtime constraints (videogames, flight control systems, etc.) your app will probably be fast enough. You'd be surprised how fast computers are nowadays... >If you are locking memory/data structure locations you are likely doing something wrong. Oh, concurrent memory fences! Well, that solves all my pr…

    All I have to do is give up all hope of my code ever
    being portable to multiple architectures
You know nothing about modern compiler atomics.

Post 2011 compilers (LLVM, GCC, MSVC, ICC) standardized generic memory fences for C++ and C. These are fully portable as the compiler itself determines how the layout of Acquire/Releases needs to be changed on platform you are compiling too.

Atomics are supported on ARM, x64, MIPS64, SPARC64, POWER8, POWER9...

So they're very portable. The compiler even manages removing unneeded fences. Like if you add an acquire fence after a CAS load. The CAS load is an acquire fence on x64, but not on POWER8.

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

#95
Endorsed.

Even fork _with_ exec can be real trouble. This is one of my bugaboos at work: due to poor life choices and high pain tolerance, I own the infrastructure we use to spawn subprocesses (carefully.) For various reasons (security most notably) we have to do some very tricky things in and to the forked child before exec(), and pretty much all of this code is a disaster waiting to happen. Every so often I get feature requests for more stupid pet tricks people would like out of subprocesses, and they're always surprised by what their "simple" change would entail.

I'd like it if Linux had native support for posix_spawn, but even that would require a lot of extensions to be useful.

Don't get me started on the teams that want to break forking rules and thus ask me how to guarantee a process has no non-main threads. There are few ways you can make me more upset than by building software that breaks if some one else happens to call pthread_create and doesn't tell you.

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

#96
post #55

Earlier quoted context omitted.

What makes for simple? I really need to understand this. I implemented fork many years ago for a university operating system and nothing about either the implementation nor the usage is simple. I use fork on a daily basis and the POSIX version of it is insanely complex for everybody involved.

> I implemented fork many years ago for a university operating system and nothing about either the implementation nor the usage is simple Me too. I found it simple: Create new child process, copy the address space, copy the instruction pointer, let it run. If you want to improve performance via copy-on-write, it gets more complex to implement. If you combine it with other features like threads, mmap, limits, etc use…

> Me too. I found it simple: Create new child process, copy the address space, copy the instruction pointer, let it run.

Even in the total absence of threads (which you never have on a modern OS) you already fucked up. Because you did not account for file handles, signal handling, pid management etc.

fork() even on the simplest real world operating system is a massive pain for the OS.

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

#97
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.

You can set all the sensible defaults automatically. In fact, most spawn APIs do set sensible defaults.

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

#98
post #28

Earlier quoted context omitted.

true... ...this applies not just to threading, but other things that are not in the domain of the library such as memory allocation strategy or communication mechanism. Libraries should be small, focused and decoupled. This is the way to proper re-usability.

Like left-pad?

There's nothing really wrong with packaging small isolated pieces of code. The debacle came from consumers' bad habit of randomly upgrading their dependencies without doing full regression testing (partly because the tooling encourages this terrible practice).

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

#99

Earlier quoted context omitted.

Cache hit rates aren't as important as you seem to think. If you're not google, and your app doesn't have hard/soft realtime constraints (videogames, flight control systems, etc.) your app will probably be fast enough. You'd be surprised how fast computers are nowadays... >If you are locking memory/data structure locations you are likely doing something wrong. Oh, concurrent memory fences! Well, that solves all my pr…

All I have to do is give up all hope of my code ever being portable to multiple architectures You know nothing about modern compiler atomics. Post 2011 compilers (LLVM, GCC, MSVC, ICC) standardized generic memory fences for C++ and C. These are fully portable as the compiler itself determines how the layout of Acquire/Releases needs to be changed on platform you are compiling too. Atomics are supported on ARM, x64, M…

Okay, what about HLLs? Memory fences don't work if you aren't writing C. And as I've said, forks are an easier way to get these guarantees, albeit at a moderate perf cost.

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

#100
post #21

Earlier quoted context omitted.

You can have your cake and eat it too -- there's no throwing away of performance gains. Libraries can use threads, but not directly or "silently". They should take threads as parameters. Then the application knows when they're being used and the application programmer can reason about it.

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?

I've used OpenMP a little bit, and I would probably avoid it for libraries.

I might use it to start out an application, since it requires very little code for dumb parallelism. But if I were making a real library, I would try to abstract the concurrency policy away so the user can control it.

This should be trivial by layering the library. Just provide a bunch of single-threaded functions, and then some OpenMP wrappers. Then user can call the single-threaded functions if she wants to do something different.

What I don't want is OpenMP baked deeply into the program logic. I think it shares the problem I was talking about. If you have 10 different libraries using OpenMP in an app, and the app is ITSELF multi-threaded, it seems like you have a mess of threads that you have no control over.

Post reply on HN