We do not promise API stability for non-C languages; this is an API break as follows: instead of calling 'NBDKit.register_plugin model plugin' with a static model, you can now add .thread_model :(unit -> thread_model) to plugin or default to PARALLEL.
Since all existing OCaml plugins will have already thought about thread models, they can convert their existing model into the new plugin field (and thus, I don't feel too bad making PARALLEL the default, even if it is not always the safest). Signed-off-by: Eric Blake <ebl...@redhat.com> --- I'm still looking at two followups: 1) ./nbdkit doesn't set LD_LIBRARY_PATH=plugins/ocaml/.libs:$LD_LIBRARY_PATH (making ./nbdkit --dump-plugin tests/test-ocaml-plugin.so fail to load when the system nbdkit is too old) 2) although --dump-plugin shows thread model, ./nbdkit -v log does not; I need to add a debug() statement for that in server/locks.c But I was quite pleased that I got this working in under 3 hours (I'm getting better at OCaml). plugins/ocaml/nbdkit-ocaml-plugin.pod | 13 ++++++----- plugins/ocaml/ocaml.c | 33 +++++++++++++++++++++------ plugins/ocaml/NBDKit.ml | 28 ++++++++++++++--------- plugins/ocaml/NBDKit.mli | 19 ++++++++------- plugins/ocaml/example.ml | 9 +++++--- tests/test_ocaml_plugin.ml | 5 ++-- 6 files changed, 69 insertions(+), 38 deletions(-) diff --git a/plugins/ocaml/nbdkit-ocaml-plugin.pod b/plugins/ocaml/nbdkit-ocaml-plugin.pod index a66cf26e..4b349612 100644 --- a/plugins/ocaml/nbdkit-ocaml-plugin.pod +++ b/plugins/ocaml/nbdkit-ocaml-plugin.pod @@ -36,12 +36,11 @@ Your OCaml code should call C<NBDKit.register_plugin> like this: open_connection = Some myplugin_open; get_size = Some myplugin_get_size; pread = Some myplugin_pread; + thread_model = Some (fun () -> NBDKit.THREAD_MODEL_SERIALIZE_CONNECTIONS); (* etc *) } - let thread_model = NBDKit.THREAD_MODEL_SERIALIZE_CONNECTIONS - - let () = NBDKit.register_plugin thread_model plugin + let () = NBDKit.register_plugin plugin Your plugin must call C<register_plugin> exactly once when the plugin is loaded. @@ -108,9 +107,11 @@ to control this. =head2 Threads -The first parameter of C<NBDKit.register_plugin> is the thread model, -which can be one of the values in the table below. For more -information on thread models, see L<nbdkit-plugin(3)/THREADS>. Note +One of the members in the plugin record passed to +C<NBDKit.register_plugin> is C<thread model>, which must return one of +the values in the table below. For more information on thread models, +see L<nbdkit-plugin(3)/THREADS>. If this optional function is not +provided, the thread model defaults to THREAD_MODEL_PARALLEL. Note that because of the garbage collector lock in OCaml, callbacks are never truly concurrent. diff --git a/plugins/ocaml/ocaml.c b/plugins/ocaml/ocaml.c index f664a7fb..01f4448f 100644 --- a/plugins/ocaml/ocaml.c +++ b/plugins/ocaml/ocaml.c @@ -72,6 +72,7 @@ static void remove_roots (void); static struct nbdkit_plugin plugin = { ._struct_size = sizeof (plugin), ._api_version = NBDKIT_API_VERSION, + ._thread_model = NBDKIT_THREAD_MODEL_PARALLEL, /* The following field is used as a canary to detect whether the * OCaml code started up and called us back successfully. If it's @@ -131,6 +132,8 @@ static value extents_fn; static value can_cache_fn; static value cache_fn; +static value thread_model_fn; + /*----------------------------------------------------------------------*/ /* Wrapper functions that translate calls from C (ie. nbdkit) to OCaml. */ @@ -683,18 +686,30 @@ cache_wrapper (void *h, uint32_t count, uint64_t offset, uint32_t flags) CAMLreturnT (int, 0); } +static int +thread_model_wrapper (void) +{ + CAMLparam0 (); + CAMLlocal1 (rv); + + caml_leave_blocking_section (); + + rv = caml_callback_exn (config_complete_fn, Val_unit); + if (Is_exception_result (rv)) { + nbdkit_error ("%s", caml_format_exception (Extract_exception (rv))); + caml_enter_blocking_section (); + CAMLreturnT (int, -1); + } + + caml_enter_blocking_section (); + CAMLreturnT (int, Int_val (rv)); +} + /*----------------------------------------------------------------------*/ /* set_* functions called from OCaml code at load time to initialize * fields in the plugin struct. */ -value -ocaml_nbdkit_set_thread_model (value modelv) -{ - plugin._thread_model = Int_val (modelv); - return Val_unit; -} - value ocaml_nbdkit_set_name (value namev) { @@ -775,6 +790,8 @@ SET(extents) SET(can_cache) SET(cache) +SET(thread_model) + #undef SET static void @@ -817,6 +834,8 @@ remove_roots (void) REMOVE (can_cache); REMOVE (cache); + REMOVE (thread_model); + #undef REMOVE } diff --git a/plugins/ocaml/NBDKit.ml b/plugins/ocaml/NBDKit.ml index 02aa2001..57e57a46 100644 --- a/plugins/ocaml/NBDKit.ml +++ b/plugins/ocaml/NBDKit.ml @@ -1,3 +1,4 @@ +(* hey emacs, this is OCaml code: -*- tuareg -*- *) (* nbdkit OCaml interface * Copyright (C) 2014-2019 Red Hat Inc. * @@ -39,6 +40,12 @@ type fua_flag = FuaNone | FuaEmulate | FuaNative type cache_flag = CacheNone | CacheEmulate | CacheNop +type thread_model = +| THREAD_MODEL_SERIALIZE_CONNECTIONS +| THREAD_MODEL_SERIALIZE_ALL_REQUESTS +| THREAD_MODEL_SERIALIZE_REQUESTS +| THREAD_MODEL_PARALLEL + type extent = { offset : int64; length : int64; @@ -87,6 +94,8 @@ type 'a plugin = { can_cache : ('a -> cache_flag) option; cache : ('a -> int32 -> int64 -> flags -> unit) option; + + thread_model : (unit -> thread_model) option; } let default_callbacks = { @@ -130,16 +139,10 @@ let default_callbacks = { can_cache = None; cache = None; + + thread_model = None; } -type thread_model = -| THREAD_MODEL_SERIALIZE_CONNECTIONS -| THREAD_MODEL_SERIALIZE_ALL_REQUESTS -| THREAD_MODEL_SERIALIZE_REQUESTS -| THREAD_MODEL_PARALLEL - -external set_thread_model : int -> unit = "ocaml_nbdkit_set_thread_model" "noalloc" - external set_name : string -> unit = "ocaml_nbdkit_set_name" "noalloc" external set_longname : string -> unit = "ocaml_nbdkit_set_longname" "noalloc" external set_version : string -> unit = "ocaml_nbdkit_set_version" "noalloc" @@ -181,9 +184,11 @@ external set_extents : ('a -> int32 -> int64 -> flags -> extent list) -> unit = external set_can_cache : ('a -> cache_flag) -> unit = "ocaml_nbdkit_set_can_cache" external set_cache : ('a -> int32 -> int64 -> flags -> unit) -> unit = "ocaml_nbdkit_set_cache" +external set_thread_model : (unit -> thread_model) -> unit = "ocaml_nbdkit_set_thread_model" "noalloc" + let may f = function None -> () | Some a -> f a -let register_plugin thread_model plugin = +let register_plugin plugin = (* Check the required fields have been set by the caller. *) if plugin.name = "" then failwith "'.name' field in NBDKit.plugin structure must be set"; @@ -198,7 +203,6 @@ let register_plugin thread_model plugin = plugin.name); (* Set the fields in the C code. *) - set_thread_model (Obj.magic thread_model); set_name plugin.name; if plugin.longname <> "" then @@ -243,7 +247,9 @@ let register_plugin thread_model plugin = may set_extents plugin.extents; may set_can_cache plugin.can_cache; - may set_cache plugin.cache + may set_cache plugin.cache; + + may set_thread_model plugin.thread_model external _set_error : int -> unit = "ocaml_nbdkit_set_error" "noalloc" diff --git a/plugins/ocaml/NBDKit.mli b/plugins/ocaml/NBDKit.mli index bab8f7f6..778250ef 100644 --- a/plugins/ocaml/NBDKit.mli +++ b/plugins/ocaml/NBDKit.mli @@ -1,3 +1,4 @@ +(* hey emacs, this is OCaml code: -*- tuareg -*- *) (* nbdkit OCaml interface * Copyright (C) 2014-2019 Red Hat Inc. * @@ -50,6 +51,13 @@ type extent = { } (** The type of the extent list returned by [.extents]. *) +type thread_model = +| THREAD_MODEL_SERIALIZE_CONNECTIONS +| THREAD_MODEL_SERIALIZE_ALL_REQUESTS +| THREAD_MODEL_SERIALIZE_REQUESTS +| THREAD_MODEL_PARALLEL +(** The type of the thread model returned by [.thread_model]. *) + type 'a plugin = { name : string; (* required *) longname : string; @@ -91,6 +99,8 @@ type 'a plugin = { can_cache : ('a -> cache_flag) option; cache : ('a -> int32 -> int64 -> flags -> unit) option; + + thread_model : (unit -> thread_model) option; } (** The plugin fields and callbacks. ['a] is the handle type. *) @@ -98,14 +108,7 @@ val default_callbacks : 'a plugin (** The plugin with all fields set to [None], so you can write [{ defaults_callbacks with field1 = Some foo1; field2 = Some foo2 }] *) -type thread_model = -| THREAD_MODEL_SERIALIZE_CONNECTIONS -| THREAD_MODEL_SERIALIZE_ALL_REQUESTS -| THREAD_MODEL_SERIALIZE_REQUESTS -| THREAD_MODEL_PARALLEL -(** The thread model. *) - -val register_plugin : thread_model -> 'a plugin -> unit +val register_plugin : 'a plugin -> unit (** Register the plugin with nbdkit. *) val set_error : Unix.error -> unit diff --git a/plugins/ocaml/example.ml b/plugins/ocaml/example.ml index 8ec6f063..45de035f 100644 --- a/plugins/ocaml/example.ml +++ b/plugins/ocaml/example.ml @@ -71,6 +71,9 @@ let ocamlexample_pwrite h buf offset _ = let offset = Int64.to_int offset in String.blit buf 0 !disk offset len +let ocamlexample_thread_model () = + NBDKit.THREAD_MODEL_SERIALIZE_CONNECTIONS + let plugin = { NBDKit.default_callbacks with (* name, open_connection, get_size and pread are required, @@ -88,8 +91,8 @@ let plugin = { get_size = Some ocamlexample_get_size; pread = Some ocamlexample_pread; pwrite = Some ocamlexample_pwrite; + + thread_model = Some ocamlexample_thread_model; } -let thread_model = NBDKit.THREAD_MODEL_SERIALIZE_CONNECTIONS - -let () = NBDKit.register_plugin thread_model plugin +let () = NBDKit.register_plugin plugin diff --git a/tests/test_ocaml_plugin.ml b/tests/test_ocaml_plugin.ml index eb0d9319..3cf8fd90 100644 --- a/tests/test_ocaml_plugin.ml +++ b/tests/test_ocaml_plugin.ml @@ -75,8 +75,7 @@ let plugin = { pwrite = Some test_pwrite; extents = Some test_extents; + thread_model = Some (fun () -> NBDKit.THREAD_MODEL_SERIALIZE_CONNECTIONS); } -let thread_model = NBDKit.THREAD_MODEL_SERIALIZE_CONNECTIONS - -let () = NBDKit.register_plugin thread_model plugin +let () = NBDKit.register_plugin plugin -- 2.20.1 _______________________________________________ Libguestfs mailing list Libguestfs@redhat.com https://www.redhat.com/mailman/listinfo/libguestfs