aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Setup.hs2
-rw-r--r--harness/CMakeLists.txt20
-rw-r--r--harness/include/plugin.h64
-rw-r--r--harness/src/main.c93
-rw-r--r--harness/src/plugin.c86
-rw-r--r--harness/tools/genbuild.pl43
-rw-r--r--harness/tools/genintf.pl26
-rw-r--r--package.yaml3
-rw-r--r--src/Wetterhorn/Core.hs26
-rw-r--r--src/Wetterhorn/FFI.hs55
-rw-r--r--src/harness_adapter.c21
11 files changed, 340 insertions, 99 deletions
diff --git a/Setup.hs b/Setup.hs
index a7a3323..50385ed 100644
--- a/Setup.hs
+++ b/Setup.hs
@@ -11,6 +11,8 @@ import Text.Printf
import Data.Maybe (fromJust)
main = do
+ putStrLn "Running Setup.hs"
+
defaultMainWithHooks $
simpleUserHooks
{ preConf = \_ conf -> do
diff --git a/harness/CMakeLists.txt b/harness/CMakeLists.txt
index b38cb77..a254afd 100644
--- a/harness/CMakeLists.txt
+++ b/harness/CMakeLists.txt
@@ -6,10 +6,28 @@ project (
include_directories(include/)
+set(PLUGIN_INTF ${CMAKE_BINARY_DIR}/plugin_interface.h)
+add_custom_command(
+ OUTPUT ${PLUGIN_INTF}
+ COMMAND perl ${PROJECT_SOURCE_DIR}/tools/genintf.pl <
+ ${PROJECT_SOURCE_DIR}/include/plugin.h > ${PLUGIN_INTF}
+ DEPENDS ${PROJECT_SOURCE_DIR}/include/plugin.h
+ DEPENDS ${PROJECT_SOURCE_DIR}/tools/genintf.pl
+)
+
+set(PLUGIN_LOAD ${CMAKE_BINARY_DIR}/gen_plugin_load.c)
+add_custom_command(
+ OUTPUT ${PLUGIN_LOAD}
+ COMMAND perl ${PROJECT_SOURCE_DIR}/tools/genbuild.pl <
+ ${PROJECT_SOURCE_DIR}/include/plugin.h > ${PLUGIN_LOAD}
+ DEPENDS ${PROJECT_SOURCE_DIR}/include/plugin.h
+ DEPENDS ${PROJECT_SOURCE_DIR}/tools/genbuild.pl
+)
+
file (GLOB_RECURSE SOURCES src/*.c)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
-add_executable (wtr_harness ${SOURCES})
+add_executable (wtr_harness ${SOURCES} ${PLUGIN_LOAD} ${PLUGIN_INTF})
target_link_libraries(wtr_harness dl)
diff --git a/harness/include/plugin.h b/harness/include/plugin.h
index 70ca198..db15401 100644
--- a/harness/include/plugin.h
+++ b/harness/include/plugin.h
@@ -2,16 +2,20 @@
#define _PLUGIN_H_
#include <dlfcn.h>
-#include <stdint.h>
#include <pthread.h>
+#include <stdint.h>
-#define PRIVATE(a) a
+/*
+ * Marker macro to define what functions should be exported. This generates the
+ * interface which the plugin needs to implement.
+ */
+#define EXPORT(a) a
-typedef void* dlhandle_t;
+typedef void *dlhandle_t;
/* Opaque state for a plugin. Not to be touched by the harness (not that it
* really can be.) */
-typedef void* opqst_t;
+typedef void *opqst_t;
/*
* Structure for the plugin.
@@ -28,25 +32,34 @@ typedef struct PLUGIN {
*
* It's guaranteed that this state is used linearly, meaning the harness gives
* up all ownership to it once passed into a handler. */
- PRIVATE(opqst_t state);
+ opqst_t state;
/* This plugin's lock. This avoids potential issues with multiple threads
* trying to change the opaque state at once which can lead to undesireable
* outcomes. */
- PRIVATE(pthread_mutex_t lock);
+ pthread_mutex_t lock;
/* The handle to the shared library. */
- PRIVATE(dlhandle_t library_handle);
+ dlhandle_t library_handle;
/* Pointer to the plugin name. This is in the shared library and a
* null-terminated string. If the library does not have a plugin name, this
* will be NULL. */
- PRIVATE(const char* plugin_name);
+ const char *plugin_name;
+
+ /**
+ * This is the first thing called on the plugin when loading it. This is
+ * different from plugin_load in that it will not be called on a hot-reload.
+ *
+ * This is used as a hack to get around GHC's limitation where it can't
+ * start up the RTS once it has been shutdown.
+ */
+ EXPORT(void (*plugin_metaload)(int argc, char** argv));
/** Intializes the plugin with the given argc/argv. This is the first thing
* called on the plugin and is called immediately after the library is loaded.
*/
- void (*plugin_load)(int argc, char** argv);
+ EXPORT(void (*plugin_load)(int argc, char **argv));
/* Start the plugin with the marshalled state from the previous plugin.
*
@@ -57,13 +70,13 @@ typedef struct PLUGIN {
* hot-reloading can produce incompatibilities between the old state and the
* new state, and this should not cause a failure.
*/
- opqst_t (*plugin_hot_start)(uint8_t* mashalled_state, uint32_t n);
+ EXPORT(opqst_t (*plugin_hot_start)(uint8_t *mashalled_state, uint32_t n));
/*
* Starts the plugin without a marshalled state. Happens during the first boot
* when there is not state.
*/
- opqst_t (*plugin_cold_start)();
+ EXPORT(opqst_t (*plugin_cold_start)());
/*
* Marshals the state to a bytestring. The returned pointer should be malloc'd
@@ -72,30 +85,29 @@ typedef struct PLUGIN {
* This is usually called in preparation for a teardown followed by a
* hot-start.
*/
- uint8_t* (*plugin_marshal_state)(opqst_t st, uint32_t* szout);
+ EXPORT(uint8_t *(*plugin_marshal_state)(opqst_t st, uint32_t *szout));
/*
* Teardown the plugin in preperation for the library's imminent unloading.
*/
- void (*plugin_teardown)(opqst_t);
-
- /* This anonymous structure contains event handlers. It is wrapped in this
- * structure to make it distinct from the management functions above.
- *
- * The handlers are all optional and if any of them are NULL, the opaque state
- * remains untouched.
- */
- struct {
- /* Right now, empty */
- } handlers;
+ EXPORT(void (*plugin_teardown)(opqst_t));
} plugin_t;
/** Loads a plugin from the dynamic library handle. Returns a non-zero error
* code on error. */
-int load_plugin_from_dlhandle(plugin_t* out, dlhandle_t library);
+int load_plugin_from_dl(dlhandle_t library, plugin_t *out);
+
+/* Reloads the plugin. This tears down the existing plugin, marshals the state
+ * for it and reloads it.
+ *
+ * This function will call dlclose on the plugin's library handle if it is not
+ * the same as 'library'.
+ */
+int plugin_hot_reload(int argc, char **argv, dlhandle_t library,
+ plugin_t *plugin);
-/* Like the above, but load the library from the given file. */
-int load_plugin_from_file(plugin_t* out, const char* dlfilename);
+/* Reads a plugin from a filename. */
+int read_plugin_from_file(const char *filename, plugin_t *plugin);
#endif /* _PLUGIN_H_ */
diff --git a/harness/src/main.c b/harness/src/main.c
index 1580bd6..648473f 100644
--- a/harness/src/main.c
+++ b/harness/src/main.c
@@ -1,69 +1,72 @@
-#include <stdio.h>
+#include "plugin.h"
+#include <ctype.h>
#include <dlfcn.h>
+#include <stdio.h>
+#include <stdlib.h>
-typedef void* dllib_t;
+typedef void *dllib_t;
-typedef void* opqst_t;
+typedef void *opqst_t;
-dllib_t open_library(const char* library, int* err)
-{
- dllib_t lib = dlopen(library, RTLD_LAZY);
+void shx(uint8_t *state, uint32_t sz) {
+ uint32_t i = 0;
+ while (i < sz) {
+ for (int j = 0; j < 16; ++j) {
+ if (i < sz) {
+ printf("%02x ", (unsigned int)state[i]);
+ } else {
+ printf(" ");
+ }
+ ++i;
+ }
- if (!lib) {
- fprintf(stderr, "Error opening shared library: %s\n", dlerror());
- *err = 1;
- }
- *err = 0;
- return lib;
-}
+ i -= 16;
+
+ printf(" ");
-void* getsym(dllib_t handle, char* sym, int* err)
-{
- void* ret = dlsym(handle, sym);
- if (!ret) {
- fprintf(stderr, "Unable to read symbol '%s'\n", sym);
- *err |= 1;
- return NULL;
+ for (int j = 0; j < 16; ++j) {
+ if (i < sz) {
+ if (isprint(state[i]) && !isspace(state[i])) {
+ printf("%c", state[i]);
+ } else {
+ printf(".");
+ }
+ } else {
+ printf(" ");
+ }
+ ++i;
+ }
+ printf("\n");
}
- return ret;
}
-void use_library(int argc, char** argv, dllib_t lib, int* err)
-{
- *err = 0;
+void run_plugin(int argc, char **argv, plugin_t *plugin) {
+ fprintf(stderr, "Running plugin: %s\n", plugin->plugin_name);
- opqst_t (*init)(int* argc, char*** argv) = getsym(lib, "plugin_init", err);
- void (*teardown)() = getsym(lib, "plugin_teardown", err);
- opqst_t (*sym)(opqst_t) = getsym(lib, "handle_thing", err);
+ uint32_t sz;
- if (*err) {
- fprintf(stderr, "Unable to find symbol call_in.\n");
- return;
- }
+ plugin_hot_reload(argc, argv, plugin->library_handle, plugin);
- opqst_t st = init(&argc, &argv);
- for (int i = 0; i < 100000; ++ i) {
- st = sym(st);
- printf("stable_ptr: %p\n", st);
- }
- teardown();
+ return;
}
-int main(int argc, char** argv)
-{
+int main(int argc, char **argv) {
if (!argv[0] && !argv[1]) {
fprintf(stderr, "Missing argument.\n");
return 1;
}
- int err = 0;
- dllib_t lib = open_library(argv[1], &err);
-
- if (err) {
- return err;
+ plugin_t plugin;
+ if (read_plugin_from_file(argv[1], &plugin)) {
+ fprintf(stderr, "Exiting due to other failures.\n");
+ return 1;
}
+ plugin.plugin_metaload(argc, argv);
+ plugin.plugin_load(argc, argv);
+ plugin.state = plugin.plugin_cold_start();
+
+ run_plugin(argc, argv, &plugin);
- use_library(argc, argv, lib, &err);
return 0;
}
diff --git a/harness/src/plugin.c b/harness/src/plugin.c
new file mode 100644
index 0000000..9b71c49
--- /dev/null
+++ b/harness/src/plugin.c
@@ -0,0 +1,86 @@
+#include "plugin.h"
+
+#include <ctype.h>
+#include <dlfcn.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+static void shx(uint8_t *state, uint32_t sz) {
+ uint32_t i = 0;
+ while (i < sz) {
+ for (int j = 0; j < 16; ++j) {
+ if (i < sz) {
+ printf("%02x ", (unsigned int)state[i]);
+ } else {
+ printf(" ");
+ }
+ ++i;
+ }
+
+ i -= 16;
+
+ printf(" ");
+
+ for (int j = 0; j < 16; ++j) {
+ if (i < sz) {
+ if (isprint(state[i]) && !isspace(state[i])) {
+ printf("%c", state[i]);
+ } else {
+ printf(".");
+ }
+ } else {
+ printf(" ");
+ }
+ ++i;
+ }
+ printf("\n");
+ }
+}
+
+int read_plugin_from_file(const char *filename, plugin_t *plugin) {
+ dlhandle_t lib = dlopen(filename, RTLD_LAZY);
+
+ if (!lib) {
+ fprintf(stderr, "Failed to open library: %s: %s\n", filename, dlerror());
+ return 1;
+ }
+
+ return load_plugin_from_dl(lib, plugin);
+}
+
+int plugin_hot_reload(int argc, char **argv, dlhandle_t library,
+ plugin_t *plugin) {
+ int ec = 0;
+ uint32_t sz = 0;
+ uint8_t *marshalled_state = NULL;
+
+ printf("Hot Reloading %s\n", plugin->plugin_name);
+ pthread_mutex_lock(&plugin->lock);
+
+ printf("Marshalling state ...\n");
+ marshalled_state = plugin->plugin_marshal_state(plugin->state, &sz);
+
+ printf("Calling teardown ...\n");
+ plugin->plugin_teardown(plugin->state);
+
+ printf("State Marshalled:\n");
+ shx(marshalled_state, sz);
+
+ if (library != plugin->library_handle) {
+ dlclose(plugin->library_handle);
+ }
+
+ if ((ec = load_plugin_from_dl(library, plugin))) {
+ goto fail;
+ }
+
+ printf("Loading plugin ...\n");
+ plugin->plugin_load(argc, argv);
+ printf("Hot starting plugin ...\n");
+ plugin->state = plugin->plugin_hot_start(marshalled_state, sz);
+
+fail:
+ free(marshalled_state);
+ pthread_mutex_unlock(&plugin->lock);
+ return ec;
+}
diff --git a/harness/tools/genbuild.pl b/harness/tools/genbuild.pl
new file mode 100644
index 0000000..b17ab6a
--- /dev/null
+++ b/harness/tools/genbuild.pl
@@ -0,0 +1,43 @@
+#!/usr/bin/env perl
+
+$comment="";
+
+print "#include <stdio.h>\n";
+print "#include <dlfcn.h>\n";
+print "#include <pthread.h>\n";
+print "#include \"plugin.h\"\n\n";
+
+print "int load_plugin_from_dl(dlhandle_t dl, plugin_t* plug)\n";
+print "{\n";
+print " void* ptr;\n";
+print " int ret = 0;\n";
+print "\n";
+print " const char** name = dlsym(dl, \"plugin_name\");\n";
+print " if (name) {\n";
+print " plug->plugin_name = *name;\n";
+print " } else {\n";
+print " plug->plugin_name = NULL;\n";
+print " }\n";
+print " plug->state = NULL;\n";
+print " plug->library_handle = dl;\n";
+print "\n";
+while (<>) {
+ if (/^\s*EXPORT\(\s*((?:\w|\s*\*\s*)+)\s*\(\*(\w+)\)\s*\((.*)\)\);/) {
+ print "\n";
+ print " ptr = dlsym(dl, \"$2\");\n";
+ print " if (!ptr) {\n";
+ print " fprintf(stderr, \"Plugin missing %s\\n\", \"$2\");\n";
+ print " ret |= 1;\n";
+ print " }\n";
+ print " plug->$2 = ptr;\n";
+ $comment="";
+ }
+}
+print "\n if (!ret) {\n";
+print " if(pthread_mutex_init(&plug->lock, NULL)) {\n";
+print " fprintf(stderr, \"Failed to initalize plugin lock\");\n";
+print " ret |= 1;\n";
+print " }\n";
+print " }\n";
+print "\n return ret;\n";
+print "}\n";
diff --git a/harness/tools/genintf.pl b/harness/tools/genintf.pl
new file mode 100644
index 0000000..6e9d477
--- /dev/null
+++ b/harness/tools/genintf.pl
@@ -0,0 +1,26 @@
+#!/usr/bin/env perl
+
+$comment="";
+
+print "#ifndef _PLUG_INTF\n";
+print "#define _PLUG_INTF\n";
+print "\n#include <stdint.h>\n";
+print "\ntypedef void* opqst_t;\n";
+while (<>) {
+ if (/^\s*\/\*/) {
+ $_ =~ s/^\s*//;
+ $comment="$_";
+ }
+
+ if (/^\s*\*/) {
+ $_ =~ s/^\s*/ /;
+ $comment="$comment$_";
+ }
+
+ if (/^\s*EXPORT\(\s*((?:\w|\s*\*\s*)+)\s*\(\*(\w+)\)\s*\((.*)\)\);/) {
+ print "$comment";
+ print "$1 $2($3);\n\n";
+ $comment="";
+ }
+}
+print "#endif /* _PLUG_INTF */\n";
diff --git a/package.yaml b/package.yaml
index 73d5e50..0dbf9a4 100644
--- a/package.yaml
+++ b/package.yaml
@@ -31,6 +31,7 @@ custom-setup:
dependencies:
- base >= 4.7 && < 5
- mtl
+- bytestring
ghc-options:
- -Wall
@@ -42,6 +43,7 @@ ghc-options:
- -Wmissing-home-modules
- -Wpartial-fields
- -Wredundant-constraints
+- -XTupleSections
- -fPIC
executables:
@@ -56,6 +58,7 @@ executables:
cc-options:
- -g3
- -shared
+ - -Iharness/build/
tests:
wetterhorn-test:
diff --git a/src/Wetterhorn/Core.hs b/src/Wetterhorn/Core.hs
index 5ce93f0..e04cb49 100644
--- a/src/Wetterhorn/Core.hs
+++ b/src/Wetterhorn/Core.hs
@@ -11,14 +11,18 @@ module Wetterhorn.Core
initWetterhorn,
wio,
incrementState,
+ readWState,
)
where
-import Control.Applicative
import Control.Arrow (first)
-import Foreign (StablePtr, deRefStablePtr, freeStablePtr, newStablePtr)
+import Control.Exception
+import Data.ByteString (ByteString)
+import qualified Data.ByteString.Char8 as CH
+import Foreign (StablePtr, newStablePtr)
-type Wetterhorn = (StablePtr (WConfig, WState))
+-- This is this opaque state presented to the harness.
+type Wetterhorn = StablePtr (WConfig, WState)
initWetterhorn :: WConfig -> IO Wetterhorn
initWetterhorn conf = do
@@ -28,20 +32,20 @@ data WState = WState
{ someString :: String,
integer :: Int
}
+ deriving (Show, Read)
data WConfig = WConfig
{ someConfig :: String,
handleSomething :: W ()
}
-foreign export ccall handle_thing :: StablePtr (WConfig, WState) -> IO (StablePtr (WConfig, WState))
-
-handle_thing :: StablePtr (WConfig, WState) -> IO (StablePtr (WConfig, WState))
-handle_thing ptr = do
- (conf, st) <- deRefStablePtr ptr
- (_, st') <- runW (handleSomething conf) (conf, st)
- newStablePtr (conf, st')
- <* freeStablePtr ptr
+readWState :: ByteString -> IO WState
+readWState bs =
+ catch
+ (return $ read (CH.unpack bs))
+ ( \e ->
+ let _ = (e :: SomeException) in return (WState "" 0)
+ )
newtype W a = W ((WConfig, WState) -> IO (a, WState))
diff --git a/src/Wetterhorn/FFI.hs b/src/Wetterhorn/FFI.hs
new file mode 100644
index 0000000..c27c129
--- /dev/null
+++ b/src/Wetterhorn/FFI.hs
@@ -0,0 +1,55 @@
+-- | This module does not export anything. It exists simply to provide C-symbols
+-- for the plugin.
+module Wetterhorn.FFI() where
+
+import Control.Monad (forM_)
+import qualified Data.ByteString as BS
+import qualified Data.ByteString.Char8 as CH
+import Foreign
+ ( Ptr,
+ Storable (poke, pokeByteOff),
+ Word32,
+ Word8,
+ deRefStablePtr,
+ freeStablePtr,
+ mallocBytes,
+ newStablePtr,
+ )
+import Foreign.C (CChar)
+import Wetterhorn.Core
+
+foreign import ccall wetterhorn :: IO Wetterhorn
+
+-- | This function is the implementation of the "hotstart" mechanism. It gives a
+-- pointer to the previously marshalled state and the length of that array and
+-- this function returns a Wetterhorn instance.
+foreign export ccall "plugin_hot_start"
+ pluginHotStart ::
+ Ptr CChar -> Word32 -> IO Wetterhorn
+
+pluginHotStart :: Ptr CChar -> Word32 -> IO Wetterhorn
+pluginHotStart chars len = do
+ bs <- BS.packCStringLen (chars, fromIntegral len)
+ wtrPtr <- wetterhorn
+ (conf, _) <- deRefStablePtr wtrPtr
+ freeStablePtr wtrPtr
+ newStablePtr . (conf,) =<< readWState bs
+
+foreign export ccall "plugin_cold_start"
+ pluginColdStart :: IO Wetterhorn
+
+pluginColdStart :: IO Wetterhorn
+pluginColdStart = wetterhorn
+
+foreign export ccall "plugin_marshal_state"
+ pluginMarshalState :: Wetterhorn -> Ptr Word32 -> IO (Ptr Word8)
+
+pluginMarshalState :: Wetterhorn -> Ptr Word32 -> IO (Ptr Word8)
+pluginMarshalState stblptr outlen = do
+ (_, st) <- deRefStablePtr stblptr
+ let bs = CH.pack (show st)
+ ret <- mallocBytes (BS.length bs)
+ poke outlen (fromIntegral $ BS.length bs)
+ forM_ (zip [0 ..] (BS.unpack bs)) $ \(off, w8) -> do
+ pokeByteOff ret off w8
+ return ret
diff --git a/src/harness_adapter.c b/src/harness_adapter.c
index dbae57a..15a2269 100644
--- a/src/harness_adapter.c
+++ b/src/harness_adapter.c
@@ -1,19 +1,8 @@
-#include <stdio.h>
#include "HsFFI.h"
+#include "plugin_interface.h"
-typedef void* opst_t;
+const char *plugin_name = "Wetterhorn";
-extern opst_t wetterhorn();
-
-opst_t plugin_init(int* argc, char*** argv)
-{
- hs_init(argc, argv);
- return wetterhorn();
-}
-
-void plugin_teardown()
-{
- hs_exit();
-}
-
-const char* plugin_name = "Wetterhorn";
+void plugin_load(int argc, char **argv) { hs_init(&argc, &argv); }
+void plugin_metaload(int argc, char **argv) { hs_init(&argc, &argv); }
+void plugin_teardown(opqst_t st) { hs_exit(); }