xref: /wasmtime-44.0.1/examples/async.cc (revision bbd12e92)
1 /*
2 Example of instantiating of the WebAssembly module and invoking its exported
3 function.
4 
5 You can compile and run this example on Linux with:
6 
7    cargo build --release -p wasmtime-c-api
8    c++ examples/async.cc \
9        -I crates/c-api/include \
10        target/release/libwasmtime.a \
11        -std=c++11 \
12        -lpthread -ldl -lm \
13        -o async
14    ./async
15 
16 Note that on Windows and macOS the command will be similar, but you'll need
17 to tweak the `-lpthread` and such annotations.
18 
19 You can also build using cmake:
20 
21 mkdir build && cd build && cmake .. && cmake --build . --target wasmtime-async
22 */
23 
24 #include <array>
25 #include <assert.h>
26 #include <chrono>
27 #include <cstdlib>
28 #include <fstream>
29 #include <future>
30 #include <iostream>
31 #include <memory>
32 #include <optional>
33 #include <sstream>
34 #include <streambuf>
35 #include <string>
36 #include <thread>
37 #include <wasmtime.h>
38 
39 namespace {
40 
41 template <typename T, void (*fn)(T *)> struct deleter {
42   void operator()(T *ptr) { fn(ptr); }
43 };
44 template <typename T, void (*fn)(T *)>
45 using handle = std::unique_ptr<T, deleter<T, fn>>;
46 
47 void exit_with_error(std::string msg, wasmtime_error_t *err,
48                      wasm_trap_t *trap) {
49   std::cerr << "error: " << msg << std::endl;
50   wasm_byte_vec_t error_message;
51   if (err) {
52     wasmtime_error_message(err, &error_message);
53   } else {
54     wasm_trap_message(trap, &error_message);
55   }
56   std::cerr << std::string(error_message.data, error_message.size) << std::endl;
57   wasm_byte_vec_delete(&error_message);
58   std::exit(1);
59 }
60 
61 handle<wasm_engine_t, wasm_engine_delete> create_engine() {
62   wasm_config_t *config = wasm_config_new();
63   assert(config != nullptr);
64   wasmtime_config_async_support_set(config, true);
65   wasmtime_config_consume_fuel_set(config, true);
66   handle<wasm_engine_t, wasm_engine_delete> engine;
67   // this takes ownership of config
68   engine.reset(wasm_engine_new_with_config(config));
69   assert(engine);
70   return engine;
71 }
72 
73 handle<wasmtime_store_t, wasmtime_store_delete>
74 create_store(wasm_engine_t *engine) {
75   handle<wasmtime_store_t, wasmtime_store_delete> store;
76   store.reset(wasmtime_store_new(engine, nullptr, nullptr));
77   assert(store);
78   return store;
79 }
80 
81 handle<wasmtime_linker_t, wasmtime_linker_delete>
82 create_linker(wasm_engine_t *engine) {
83   handle<wasmtime_linker_t, wasmtime_linker_delete> linker;
84   linker.reset(wasmtime_linker_new(engine));
85   assert(linker);
86   return linker;
87 }
88 
89 handle<wasmtime_module_t, wasmtime_module_delete>
90 compile_wat_module_from_file(wasm_engine_t *engine,
91                              const std::string &filename) {
92   std::ifstream t(filename);
93   std::stringstream buffer;
94   buffer << t.rdbuf();
95   if (t.bad()) {
96     std::cerr << "error reading file: " << filename << std::endl;
97     std::exit(1);
98   }
99   const std::string &content = buffer.str();
100   wasm_byte_vec_t wasm_bytes;
101   handle<wasmtime_error_t, wasmtime_error_delete> error{
102       wasmtime_wat2wasm(content.data(), content.size(), &wasm_bytes)};
103   if (error) {
104     exit_with_error("failed to parse wat", error.get(), nullptr);
105   }
106   wasmtime_module_t *mod_ptr = nullptr;
107   error.reset(wasmtime_module_new(engine,
108                                   reinterpret_cast<uint8_t *>(wasm_bytes.data),
109                                   wasm_bytes.size, &mod_ptr));
110   wasm_byte_vec_delete(&wasm_bytes);
111   handle<wasmtime_module_t, wasmtime_module_delete> mod{mod_ptr};
112   if (!mod) {
113     exit_with_error("failed to compile module", error.get(), nullptr);
114   }
115   return mod;
116 }
117 
118 class printer_thread_state {
119 public:
120   void set_value_to_print(int32_t v) {
121     _print_finished_future = _print_finished.get_future();
122     _value_to_print.set_value(v);
123   }
124   int32_t get_value_to_print() { return _value_to_print.get_future().get(); }
125 
126   bool print_is_pending() const {
127     return _print_finished_future.valid() &&
128            _print_finished_future.wait_for(std::chrono::seconds(0)) !=
129                std::future_status::ready;
130   }
131   void wait_for_print_result() const { _print_finished_future.wait(); }
132   void get_print_result() { _print_finished_future.get(); }
133   void set_print_success() { _print_finished.set_value(); }
134 
135 private:
136   std::promise<int32_t> _value_to_print;
137   std::promise<void> _print_finished;
138   std::future<void> _print_finished_future;
139 };
140 
141 printer_thread_state printer_state;
142 
143 struct async_call_env {
144   wasm_trap_t **trap_ret;
145 };
146 
147 bool poll_print_finished_state(void *env) {
148   std::cout << "polling async host function result" << std::endl;
149   auto *async_env = static_cast<async_call_env *>(env);
150   // Don't block, just poll the future state.
151   if (printer_state.print_is_pending()) {
152     return false;
153   }
154   try {
155     printer_state.get_print_result();
156   } catch (const std::exception &ex) {
157     std::string msg = ex.what();
158     *async_env->trap_ret = wasmtime_trap_new(msg.data(), msg.size());
159   }
160   return true;
161 }
162 } // namespace
163 
164 int main() {
165   // A thread that will async perform host function calls.
166   std::thread printer_thread([]() {
167     int32_t value_to_print = printer_state.get_value_to_print();
168     std::cout << "received value to print!" << std::endl;
169     std::this_thread::sleep_for(std::chrono::milliseconds(500));
170     std::cout << "printing: " << value_to_print << std::endl;
171     std::this_thread::sleep_for(std::chrono::milliseconds(500));
172     std::cout << "signaling that value is printed" << std::endl;
173     printer_state.set_print_success();
174   });
175 
176   handle<wasmtime_error_t, wasmtime_error_delete> error;
177 
178   auto engine = create_engine();
179   auto store = create_store(engine.get());
180   // This pointer is unowned.
181   auto *context = wasmtime_store_context(store.get());
182   // Configure the store to periodically yield control
183   wasmtime_context_set_fuel(context, 100000);
184   wasmtime_context_fuel_async_yield_interval(context, /*interval=*/10000);
185 
186   auto compiled_module =
187       compile_wat_module_from_file(engine.get(), "examples/async.wat");
188 
189   auto linker = create_linker(engine.get());
190   static std::string host_module_name = "host";
191   static std::string host_func_name = "print";
192 
193   // Declare our async host function's signature and definition.
194   wasm_valtype_vec_t arg_types;
195   wasm_valtype_vec_t result_types;
196   wasm_valtype_vec_new_uninitialized(&arg_types, 1);
197   arg_types.data[0] = wasm_valtype_new_i32();
198   wasm_valtype_vec_new_empty(&result_types);
199   handle<wasm_functype_t, wasm_functype_delete> functype{
200       wasm_functype_new(&arg_types, &result_types)};
201 
202   error.reset(wasmtime_linker_define_async_func(
203       linker.get(), host_module_name.data(), host_module_name.size(),
204       host_func_name.data(), host_func_name.size(), functype.get(),
205       [](void *, wasmtime_caller_t *, const wasmtime_val_t *args, size_t,
206          wasmtime_val_t *, size_t, wasm_trap_t **trap_ret,
207          wasmtime_async_continuation_t *continuation_ret) {
208         std::cout << "invoking async host function" << std::endl;
209         printer_state.set_value_to_print(args[0].of.i32);
210 
211         continuation_ret->callback = &poll_print_finished_state;
212         continuation_ret->env = new async_call_env{trap_ret};
213         continuation_ret->finalizer = [](void *env) {
214           std::cout << "deleting async_call_env" << std::endl;
215           delete static_cast<async_call_env *>(env);
216         };
217       },
218       /*env=*/nullptr, /*finalizer=*/nullptr));
219   if (error) {
220     exit_with_error("failed to define host function", error.get(), nullptr);
221   }
222 
223   // Now instantiate our module using the linker.
224   handle<wasmtime_call_future_t, wasmtime_call_future_delete> call_future;
225   wasm_trap_t *trap_ptr = nullptr;
226   wasmtime_error_t *error_ptr = nullptr;
227   wasmtime_instance_t instance;
228   call_future.reset(wasmtime_linker_instantiate_async(
229       linker.get(), context, compiled_module.get(), &instance, &trap_ptr,
230       &error_ptr));
231   while (!wasmtime_call_future_poll(call_future.get())) {
232     std::cout << "yielding instantiation!" << std::endl;
233   }
234   error.reset(error_ptr);
235   handle<wasm_trap_t, wasm_trap_delete> trap{trap_ptr};
236   if (error || trap) {
237     exit_with_error("failed to instantiate module", error.get(), trap.get());
238   }
239   // delete call future - it's no longer needed
240   call_future = nullptr;
241   // delete the linker now that we've created our instance
242   linker = nullptr;
243 
244   // Grab our exported function
245   static std::string guest_func_name = "print_fibonacci";
246   wasmtime_extern_t guest_func_extern;
247   bool found =
248       wasmtime_instance_export_get(context, &instance, guest_func_name.data(),
249                                    guest_func_name.size(), &guest_func_extern);
250   assert(found);
251   assert(guest_func_extern.kind == WASMTIME_EXTERN_FUNC);
252 
253   // Now call our print_fibonacci function with n=15
254   std::array<wasmtime_val_t, 1> args;
255   args[0].kind = WASMTIME_I32;
256   args[0].of.i32 = 15;
257   std::array<wasmtime_val_t, 0> results;
258   call_future.reset(wasmtime_func_call_async(
259       context, &guest_func_extern.of.func, args.data(), args.size(),
260       results.data(), results.size(), &trap_ptr, &error_ptr));
261   // Poll the execution of the call. This can yield control back if there is an
262   // async host call or if we ran out of fuel.
263   while (!wasmtime_call_future_poll(call_future.get())) {
264     // if we have an async host call pending then wait for that future to finish
265     // before continuing.
266     if (printer_state.print_is_pending()) {
267       std::cout << "waiting for async host function to complete" << std::endl;
268       printer_state.wait_for_print_result();
269       std::cout << "async host function completed" << std::endl;
270       continue;
271     }
272     // Otherwise we ran out of fuel and yielded.
273     std::cout << "yield!" << std::endl;
274   }
275   // Extract if there were failures or traps after poll returns that execution
276   // completed.
277   error.reset(error_ptr);
278   trap.reset(trap_ptr);
279   if (error || trap) {
280     exit_with_error("running guest function failed", error.get(), trap.get());
281   }
282   call_future = nullptr;
283   // At this point, if our host function returned results they would be
284   // available in the `results` array.
285   std::cout << "async function call complete!" << std::endl;
286 
287   // Join our thread and exit.
288   printer_thread.join();
289   return 0;
290 }
291