Handle stop from C/FFI

Hi all! I'm using bindgen to generate some C bindings, for now is all fine, except for one thing.

Some C functions stops the program, do not crash but stop with different status code, and this makes Rust crazy!

I'm writting a safe interface for a postgres extensions, so when there is a stop because there is an error, does not means something went bad, is just like a Result... but being unhandled by Rust...

How can catch when a C binding function stops?

You need to define what “stop” actually is; that’s not a sufficiently concrete description. Do you mean calling the exit() system call to exit with a status code? That cannot be “handled”. If it’s something else in C, then show us the C.

1 Like

Hi, checking the code, I do not know C a lot, but I found the process finish with exit(1);

So the idea would be at least be able to retrieve if this is executed and with which code.

No idea if there from C more ways to stop a program, nor how to catch a crash from C, I think would be interesting to know but lets start with the first point.

You cannot handle/cancel an exit(). Code that calls exit() is code that is not trying to cooperate with other code calling it (or is trying to report a fatal error that would otherwise corrupt the process state). The only way to make use of such code is to run it in a child process, so that the parent process can continue doing things after the child process exits.

3 Likes

In postgres this seems to be normal, so replace from C is not as good idea.

Which ways do we have to run a function a different process?

I found this: process_fun - Rust

There is also this thread: Prevent C library from crashing a Rust program? - #11

Does not specify a lot how to do it, but in this case I probs can't use WASM.

exit() is part of the interface for controlling the whole process, along with other things like argv, env, stdin, stdout, stderr. Code that calls exit() is like code that reads from stdin: it's accessing a resource belonging to the whole process, and you should not be calling it except as part of defining how the whole process behaves.

I’m not familiar with postgres’s code at all, but if it is well-designed, then this problem is a sign that either:

  • you should not be using that function, and should be calling a different function which does not exit,
  • you are attempting to recover from a fatal error (a contradiction), or
  • you are trying to use parts of postgres that are not intended to be used as a library.

Which ways do we have to run a function a different process?

Don't think of it as “run a function in a different process”. Think of it as “write a program that does the thing you want, then run that program as a child process”.

2 Likes

Thanks god you uttered the magic word, or else we would have all gone mad trying to understand what the heck you are talking about.

Postgress already does that for you. Unfortunately I have no idea about details (because I have never worked with postgress extensions), but from what I understand it works approximately like Apache: there are “master process” that forks and runs “worker process” that is supposed to be expendable.

You would need to ask on the postgresql forum, I'm afraid. And this:

Essentially means that you would need to spend a lot of time trying to understand details of how the whole thing works.

Today 99% of programs use multithreading to support many CPU cores, but Postgresql went in the entirely different direction. Doesn't mean that what you want to do is impossible… just be prepared to manually do a lot of work that normally std does for you.

Yes. Rust is entirely unprepared to work with such a baroque and bizzare organization of program. You would need extensive knowledge of C, Rust, POSIX and PostgreSQL internals (Win32, instead of POSIX, if you need to support Windows) to pull it off.

It's not just simple matter of adding few lines somewhere: you are working way outside of what Rust was designed for: multiple cooperating processes that coordinate their work via a shared memory is not something Rust design implicitly supports.

I was about to say the same thing. So one possibility (likely to me) is that when a Postgres extension calls exit that means the error is not recoverable and the process in which it runs (which are per connection) must be killed.

Sure, but there are obviously some API that is supposed to handle that and deliver information about error to the calling client, somehow.

I only ever touched PostgreSQL as a sysadmin, not a programmer, thus I don't know how it works “from inside”, but the fact that multi-process configuration allows one to simply call exit() to handle error and PostreSQL actually does that is the main reason PostgreSQL couldn't use threads, like most services do, these days, from what I understand.

Yet data in the shared memory shouldn't become inconsistent when that happens!

I don't know how the whole thing works, only have vague ideas about it, sadly.

Hi all! I'm still here, I was learning and testing things.

I do not know C, I do not know POSIX, I'm new in making PG extensions.

Here some things I know.

PG extensions works with a single thread, you can use multithread from a extension, but not call PG functions from them.

PG have an API to make it stop from C, which is ereport, still it allow the call of exit(1), in pg is not an issue, this call is done for example, if you deserialising an object and someone put trash/invalid data, is an unrecoverable situation, but this type of situations are a lot more common in PG than Rust, so is not really an issue.

C Bindings implies to work with pointers, there is no way to handle input/output using things like serde, because what matters is the data that points the pointer, this restrict a lot the options.

  1. Know exactly the data behind each pointer, be able to read from Rust its data to move a object that we can really move out using serde, then build a new pointer with this information.
  2. Use shared memory, if works use the pointer from there, if fails then no issues with that memory, I have never done something before like this.
  3. Port the C functions to Rust, which is not ideal, some projects can really be very hard to port, a lot of code, and very far from the idea of C/FFI.

And.. obvs any method used should be cross-platform, not great if a solution only works on linux!
All alternatives seems complex or slow or undesirable.

Use shared data from PGRX is not easy, for some tech reasons, probs because the element is dropped by PG and not Rust, all elements of shared data must be Copy! I have no tested this with the pointers and which would be the behavior, a pointer like *mut i8 is just a usize so should be Copy.. I think, but no idea of the memory behind will still be there without issues to use it like that.

mmm, any thoughts about this?

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.