#channel #synchronous #chan #proccesses

ceramic

Synchronous channels between proccesses

5 unstable releases

Uses old Rust 2015

0.3.0 Feb 11, 2017
0.2.1 Jan 11, 2017
0.1.3 Jan 10, 2017

#6 in #chan

35 downloads per month

MIT license

9KB
110 lines

Build Status codecov MIT licensed docs

Synchronous channels for rust between posix proccesses

Ceramic is a simple and effective way to isolate crappy code into processes. The code gets its own heap and can be terminated without affecting the main process.

It fullfills the same use case as servo/ipc-channel, but with a much more consistent and simple design. The downside is that it propbably doesn't work on windows.

This is a rust port of the original C++ library https://github.com/aep/ceramic.

Serialize and Deserialize traits are required for all types passed over the channel.

use ceramic;

fn main() {
    let chan = ceramic::channel().unwrap();

    let _p = ceramic::fork(|| {
        chan.send(&String::from("hello")).unwrap();
    });

    chan.set_timeout(Some(::std::time::Duration::new(1,0))).unwrap();
    let s : String = chan.recv().unwrap_or(Nothing).unwrap_or(String::from("nothing"));
    println!("{}", s);
}

or use it as iterator:

fn main() {
   let chan = ceramic::channel().unwrap();

   let _p = ceramic::fork(|| {
       chan.send(&String::from("herp")).unwrap();
       chan.send(&String::from("derp")).unwrap();
       chan.close().unwrap();
   });

   for s in chan {
       println!(">>{}<<", s.unwrap());
   }
}

channel synchronizing behaviour

send and receive operations must be symetric. I.e. a read() will block until write() is called from another thread, but write() will also block until there is a read(). This is essentially how golang's chan(0) behaves, and not how unix IPC usually behaves.

This makes it easy to reason about code that uses ceramic, because it introduces synchronization points.

primes = chan();
thread() {
    do {
        int nr = heavyMath();
    } while (chan << nr)
}

primes >> nr;
primes >> nr;
primes >> nr;
primes.close();

However, it also introduces possibilities for new race condition that would not exist with buffered channels, for example this is invalid:

thread() {
    write();
}
thread() {
    read();
}
write();
read();

this might look like:

  • fork some threads A and B which wait
  • then write to thread B
  • and read from thread A

but randomly the OS scheduler might decide on this order:

  • fork some threads A, B which wait
  • A writes to B
  • main thread deadlocks on write

There is an argument in golang, that you should use buffered channels only when the code works unbuffered, to avoid exactly these situations where something only doesn't deadlock because of buffers. but to a reader the codeflow is still entirely unclear.

TODO

  • api to clean up unused sockets
  • correct close write semantics are unclear
  • tests with pthread
  • deadlock detection
  • test on osx

Dependencies

~8MB
~159K SLoC