From c82691737c912c3fbfa531f571bded50417167a6 Mon Sep 17 00:00:00 2001 From: Bradlee Speice Date: Thu, 18 Jan 2018 22:58:48 -0500 Subject: [PATCH] Second Cap'N Proto blog post --- archives.html | 2 + author/bradlee-speice.html | 2 + author/bradlee-speice2.html | 2 + authors.html | 2 + captains-cookbook-part-2.html | 331 ++++++++++++++++++++++++++++++++++ categories.html | 2 + category/blog.html | 2 + category/blog2.html | 2 + index.html | 4 +- index2.html | 2 + tag/capnproto-rust.html | 2 + tags.html | 2 +- 12 files changed, 352 insertions(+), 3 deletions(-) create mode 100644 captains-cookbook-part-2.html diff --git a/archives.html b/archives.html index 118bd9e..81345a2 100644 --- a/archives.html +++ b/archives.html @@ -70,6 +70,8 @@

+
Thu 18 January 2018
+
Captain's Cookbook - Part 2
Tue 16 January 2018
Captain's Cookbook - Part 1
Tue 01 November 2016
diff --git a/author/bradlee-speice.html b/author/bradlee-speice.html index f3f8ee5..d76309f 100644 --- a/author/bradlee-speice.html +++ b/author/bradlee-speice.html @@ -70,6 +70,8 @@

+
Thu 18 January 2018
+
Captain's Cookbook - Part 2
Tue 16 January 2018
Captain's Cookbook - Part 1
Tue 01 November 2016
diff --git a/author/bradlee-speice2.html b/author/bradlee-speice2.html index f3f8ee5..d76309f 100644 --- a/author/bradlee-speice2.html +++ b/author/bradlee-speice2.html @@ -70,6 +70,8 @@

+
Thu 18 January 2018
+
Captain's Cookbook - Part 2
Tue 16 January 2018
Captain's Cookbook - Part 1
Tue 01 November 2016
diff --git a/authors.html b/authors.html index ba63d94..52cb227 100644 --- a/authors.html +++ b/authors.html @@ -70,6 +70,8 @@

Bradlee Speice

+
Thu 18 January 2018
+
Captain's Cookbook - Part 2
Tue 16 January 2018
Captain's Cookbook - Part 1
Tue 01 November 2016
diff --git a/captains-cookbook-part-2.html b/captains-cookbook-part-2.html new file mode 100644 index 0000000..e03789b --- /dev/null +++ b/captains-cookbook-part-2.html @@ -0,0 +1,331 @@ + + + + + + + + + + + Captain's Cookbook - Part 2 - Bradlee Speice + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+ + +
+
+ + + + +
+
+
+
+

Captain's Cookbook - Part 2

+

Bradlee Speice, Thu 18 January 2018, Blog

+
+
+

+ +capnproto rust

+
+
+
+
+ + + +
+ + + + +
+

Captain's Cookbook - Part 2 - Using the TypedReader

+

Part 1 of this series took a look at +a basic starting project with Cap'N Proto. In this section, we're going to take the (admittedly basic) +schema and look at how we can add a pretty basic feature - sending Cap'N Proto messages between threads. +It's nothing complex, but I want to make sure that there's some documentation surrounding practical +usage of the library.

+

As a quick refresher, we build a Cap'N Proto message and go through the serialization/deserialization steps +here. Our current example is going +to build on the code we wrote there; after the deserialization step, we'll try and send the point_reader +to a separate thread for verification.

+

I'm going to walk through the attempts as I made them and my thinking throughout. If you want to skip to the final project, +check out the code available here

+

Attempt 1: Move the reference

+

As a first attempt, we're going to try and let Rust move the reference. Our code will look something like:

+
fn main() {
+
+    // ...assume that we own a `buffer: Vec<u8>` containing the binary message content from somewhere else
+
+    let deserialized = capnp::serialize::read_message(
+        &mut buffer.as_slice(),
+        capnp::message::ReaderOptions::new()
+    ).unwrap();
+
+    let point_reader = deserialized.get_root::<point_capnp::point::Reader>().unwrap();
+
+    // By using `point_reader` inside the new thread, we're hoping that Rust can safely move
+    // the reference and invalidate the original thread's usage. Since the original thread
+    // doesn't use `point_reader` again, this should be safe, right?
+    let handle = std::thread::spawn(move || {
+
+        assert_eq!(point_reader.get_x(), 12);
+
+        assert_eq!(point_reader.get_y(), 14);
+    });
+
+    handle.join().unwrap();
+}
+
+ + +

Well, the Rust compiler doesn't really like this. We get four distinct errors back:

+
error[E0277]: the trait bound `*const u8: std::marker::Send` is not satisfied in `[closure@src/main.rs:31:37: 36:6 point_reader:point_capnp::point::Reader<'_>]`                                                                                                                
+  --> src/main.rs:31:18                                             
+   |                                                                
+31 |     let handle = std::thread::spawn(move || {                  
+   |                  ^^^^^^^^^^^^^^^^^^ `*const u8` cannot be sent between threads safely                                              
+   |                                                                
+
+error[E0277]: the trait bound `*const capnp::private::layout::WirePointer: std::marker::Send` is not satisfied in `[closure@src/main.rs:31:37: 36:6 point_reader:point_capnp::point::Reader<'_>]`                                                                               
+  --> src/main.rs:31:18                                             
+   |                                                                
+31 |     let handle = std::thread::spawn(move || {                  
+   |                  ^^^^^^^^^^^^^^^^^^ `*const capnp::private::layout::WirePointer` cannot be sent between threads safely             
+   |                                                                
+
+error[E0277]: the trait bound `capnp::private::arena::ReaderArena: std::marker::Sync` is not satisfied                                  
+  --> src/main.rs:31:18                                             
+   |                                                                
+31 |     let handle = std::thread::spawn(move || {                  
+   |                  ^^^^^^^^^^^^^^^^^^ `capnp::private::arena::ReaderArena` cannot be shared between threads safely                   
+   |                                                                
+
+error[E0277]: the trait bound `*const std::vec::Vec<std::option::Option<std::boxed::Box<capnp::private::capability::ClientHook + 'static>>>: std::marker::Send` is not satisfied in `[closure@src/main.rs:31:37: 36:6 point_reader:point_capnp::point::Reader<'_>]`             
+  --> src/main.rs:31:18                                             
+   |                                                                
+31 |     let handle = std::thread::spawn(move || {                  
+   |                  ^^^^^^^^^^^^^^^^^^ `*const std::vec::Vec<std::option::Option<std::boxed::Box<capnp::private::capability::ClientHook + 'static>>>` cannot be sent between threads safely                                                                                   
+   |                                                                
+
+error: aborting due to 4 previous errors
+
+ + +

Note, I've removed the help text for brevity, but suffice to say that these errors are intimidating. Pay attention to the text +that keeps on getting repeated though: XYZ cannot be sent between threads safely.

+

This is a bit frustrating: we own the buffer from which all the content was derived, and we don't have any unsafe accesses +in our code. We guarantee that we wait for the child thread to stop first, so there's no possibility of the pointer becoming invalid +because the original thread exits before the child thread does. So why is Rust preventing us from doing something that really should +be legal?

+

This is what is known as fighting the borrow checker. +Let our crusade begin.

+

Attempt 2: Put the Reader in a `Box

+

The Box type allows us to convert a pointer we have (in our case the +point_reader) into an "owned" value, which should be easier to send across threads. Our next attempt looks something like this:

+
fn main() {
+
+    // ...assume that we own a `buffer: Vec<u8>` containing the binary message content from somewhere else
+
+    let deserialized = capnp::serialize::read_message(
+        &mut buffer.as_slice(),
+        capnp::message::ReaderOptions::new()
+    ).unwrap();
+
+    let point_reader = deserialized.get_root::<point_capnp::point::Reader>().unwrap();
+
+    let boxed_reader = Box::new(point_reader);
+
+    // By using `point_reader` inside the new thread, we're hoping that Rust can safely move
+    // the reference and invalidate the original thread's usage. Since the original thread
+    // doesn't use `point_reader` again, this should be safe, right?
+    let handle = std::thread::spawn(move || {
+
+        assert_eq!(boxed_reader.get_x(), 12);
+
+        assert_eq!(boxed_reader.get_y(), 14);
+    });
+
+    handle.join().unwrap();
+}
+
+ + +

Spoiler alert: still doesn't work. Same errors still show up.

+
error[E0277]: the trait bound `*const u8: std::marker::Send` is not satisfied in `point_capnp::point::Reader<'_>`                       
+  --> src/main.rs:33:18                                             
+   |                                                                
+33 |     let handle = std::thread::spawn(move || {                  
+   |                  ^^^^^^^^^^^^^^^^^^ `*const u8` cannot be sent between threads safely                                              
+   |                                                                
+
+error[E0277]: the trait bound `*const capnp::private::layout::WirePointer: std::marker::Send` is not satisfied in `point_capnp::point::Reader<'_>`                                                                                                                              
+  --> src/main.rs:33:18                                             
+   |                                                                
+33 |     let handle = std::thread::spawn(move || {                  
+   |                  ^^^^^^^^^^^^^^^^^^ `*const capnp::private::layout::WirePointer` cannot be sent between threads safely             
+   |                                                                
+
+error[E0277]: the trait bound `capnp::private::arena::ReaderArena: std::marker::Sync` is not satisfied                                  
+  --> src/main.rs:33:18                                             
+   |                                                                
+33 |     let handle = std::thread::spawn(move || {                  
+   |                  ^^^^^^^^^^^^^^^^^^ `capnp::private::arena::ReaderArena` cannot be shared between threads safely                   
+   |                                                                
+
+error[E0277]: the trait bound `*const std::vec::Vec<std::option::Option<std::boxed::Box<capnp::private::capability::ClientHook + 'static>>>: std::marker::Send` is not satisfied in `point_capnp::point::Reader<'_>`                                                            
+  --> src/main.rs:33:18                                             
+   |                                                                
+33 |     let handle = std::thread::spawn(move || {                  
+   |                  ^^^^^^^^^^^^^^^^^^ `*const std::vec::Vec<std::option::Option<std::boxed::Box<capnp::private::capability::ClientHook + 'static>>>` cannot be sent between threads safely                                                                                   
+   |                                                                
+
+error: aborting due to 4 previous errors
+
+ + +

Let's be a little bit smarter about the exceptions this time though. What is that std::marker::Send +thing the compiler keeps telling us about?

+

The documentation is pretty clear; Send is used to denote:

+
+

Types that can be transferred across thread boundaries.

+
+

In our case, we are seeing the error messages for two reasons:

+
    +
  1. +

    Pointers (*const u8) are not safe to send across thread boundaries. While we're nice in our code making sure that +we wait on the child thread to finish before closing down, the Rust compiler can't make that assumption, and so complains +that we're not using this in a safe manner.

    +
  2. +
  3. +

    The point_capnp::point::Reader type is itself not safe to send across threads because it doesn't implement the Send trait. +Which is to say, the things that make up a Reader are themselves not thread-safe, so the Reader is also not thread-safe.

    +
  4. +
+

So, how are we to actually transfer a parsed Cap'N Proto message between threads?

+

Attempt 3: The TypedReader

+

The TypedReader is a new API implemented in the Cap'N Proto Rust code. We're interested +in it here for two reasons:

+
    +
  1. +

    It allows us to define an object where the object owns the underlying data. In previous attempts, the current context owned the +data, but the Reader itself had no such control.

    +
  2. +
  3. +

    We can compose the TypedReader using objects that are safe to Send across threads, guaranteeing that we can transfer +parsed messages across threads.

    +
  4. +
+

The actual code for the TypedReader is a bit complex. +And to be honest, I'm still really not sure what the whole point of the PhantomData +thing is either. My impression is that it lets us enforce type safety when we know what the underlying Cap'N Proto message represents. +That is, technically the only thing we're storing is the binary message; this just enforces the principle that the binary represents +some specific object that has been parsed.

+

Either way, we can carefully construct something which is safe to move between threads:

+
fn main() {
+
+    // ...assume that we own a `buffer: Vec<u8>` containing the binary message content from somewhere else
+
+    let deserialized = capnp::serialize::read_message(
+        &mut buffer.as_slice(),
+        capnp::message::ReaderOptions::new()
+    ).unwrap();
+
+    let point_reader: capnp::message::TypedReader<capnp::serialize::OwnedSegments, point_capnp::point::Owned> =
+        capnp::message::TypedReader::new(deserialized);
+
+    // Because the point_reader is now working with OwnedSegments (which are owned vectors) and an Owned message
+    // (which is 'static lifetime), this is now safe
+    let handle = std::thread::spawn(move || {
+
+        // The point_reader owns its data, and we use .get() to retrieve the actual point_capnp::point::Reader
+        // object from it
+        let point_root = point_reader.get().unwrap();
+
+        assert_eq!(point_root.get_x(), 12);
+
+        assert_eq!(point_root.get_y(), 14);
+    });
+
+    handle.join().unwrap();
+}
+
+ + +

And while we've left Rust to do the dirty work of actually moving the point_reader into the new thread, we could also use things +like mpsc channels to achieve a similar effect.

+

So now we're able to define basic Cap'N Proto messages, and send them all around our programs.

+

Next steps:

+

Part 1: Setting up a basic Cap'N Proto Rust project

+

Part 3: Serialization and Deserialization of multiple Cap'N Proto messages

+ + + +
+ + + + + + + \ No newline at end of file diff --git a/categories.html b/categories.html index 299a64e..685bee2 100644 --- a/categories.html +++ b/categories.html @@ -70,6 +70,8 @@

Blog

+
Thu 18 January 2018
+
Captain's Cookbook - Part 2
Tue 16 January 2018
Captain's Cookbook - Part 1
Tue 01 November 2016
diff --git a/category/blog.html b/category/blog.html index 239d1e7..c9adb60 100644 --- a/category/blog.html +++ b/category/blog.html @@ -70,6 +70,8 @@

Blog

+
Thu 18 January 2018
+
Captain's Cookbook - Part 2
Tue 16 January 2018
Captain's Cookbook - Part 1
Tue 01 November 2016
diff --git a/category/blog2.html b/category/blog2.html index 239d1e7..c9adb60 100644 --- a/category/blog2.html +++ b/category/blog2.html @@ -70,6 +70,8 @@

Blog

+
Thu 18 January 2018
+
Captain's Cookbook - Part 2
Tue 16 January 2018
Captain's Cookbook - Part 1
Tue 01 November 2016
diff --git a/index.html b/index.html index a384069..164d92f 100644 --- a/index.html +++ b/index.html @@ -71,6 +71,8 @@

+
Thu 18 January 2018
+
Captain's Cookbook - Part 2
Tue 16 January 2018
Captain's Cookbook - Part 1
Tue 01 November 2016
@@ -89,8 +91,6 @@
Predicting Santander Customer Happiness
Fri 26 February 2016
Profitability using the Investment Formula
-
Wed 03 February 2016
-
Guaranteed Money Maker
diff --git a/index2.html b/index2.html index a439898..261008e 100644 --- a/index2.html +++ b/index2.html @@ -71,6 +71,8 @@

- page 2

+
Wed 03 February 2016
+
Guaranteed Money Maker
Sat 23 January 2016
Cloudy in Seattle
Fri 01 January 2016
diff --git a/tag/capnproto-rust.html b/tag/capnproto-rust.html index 943903a..5622c9c 100644 --- a/tag/capnproto-rust.html +++ b/tag/capnproto-rust.html @@ -70,6 +70,8 @@

capnproto rust

+
Thu 18 January 2018
+
Captain's Cookbook - Part 2
Tue 16 January 2018
Captain's Cookbook - Part 1
diff --git a/tags.html b/tags.html index f4aa072..aaad639 100644 --- a/tags.html +++ b/tags.html @@ -72,7 +72,7 @@
1 article
algorithmic-trading
-
1 article
+
2 articles
capnproto rust
1 article
casino