+
+#define LWS_PLUGIN_STATIC
+#include "protocol_lws_minimal_threadpool.c"
+
+static struct lws_protocols protocols[] = {
+ { "http", lws_callback_http_dummy, 0, 0 },
+ LWS_PLUGIN_PROTOCOL_MINIMAL,
+ { NULL, NULL, 0, 0 } /* terminator */
+};
+
+static int interrupted;
+
+static const struct lws_http_mount mount = {
+ /* .mount_next */ NULL, /* linked-list "next" */
+ /* .mountpoint */ "/", /* mountpoint URL */
+ /* .origin */ "./mount-origin", /* serve from dir */
+ /* .def */ "index.html", /* default filename */
+ /* .protocol */ NULL,
+ /* .cgienv */ NULL,
+ /* .extra_mimetypes */ NULL,
+ /* .interpret */ NULL,
+ /* .cgi_timeout */ 0,
+ /* .cache_max_age */ 0,
+ /* .auth_mask */ 0,
+ /* .cache_reusable */ 0,
+ /* .cache_revalidate */ 0,
+ /* .cache_intermediaries */ 0,
+ /* .origin_protocol */ LWSMPRO_FILE, /* files in a dir */
+ /* .mountpoint_len */ 1, /* char count */
+ /* .basic_auth_login_file */ NULL,
+};
+
+/*
+ * This demonstrates how to pass a pointer into a specific protocol handler
+ * running on a specific vhost. In this case, it's our default vhost and
+ * we pass the pvo named "config" with the value a const char * "myconfig".
+ *
+ * This is the preferred way to pass configuration into a specific vhost +
+ * protocol instance.
+ */
+
+static const struct lws_protocol_vhost_options pvo_ops = {
+ NULL,
+ NULL,
+ "config", /* pvo name */
+ (void *)"myconfig" /* pvo value */
+};
+
+static const struct lws_protocol_vhost_options pvo = {
+ NULL, /* "next" pvo linked-list */
+ &pvo_ops, /* "child" pvo linked-list */
+ "lws-minimal", /* protocol name we belong to on this vhost */
+ "" /* ignored */
+};
+
+void sigint_handler(int sig)
+{
+ interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+ struct lws_context_creation_info info;
+ struct lws_context *context;
+ const char *p;
+ int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+ /* for LLL_ verbosity above NOTICE to be built into lws,
+ * lws must have been configured and built with
+ * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+ /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+ /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+ /* | LLL_DEBUG */;
+
+ signal(SIGINT, sigint_handler);
+
+ if ((p = lws_cmdline_option(argc, argv, "-d")))
+ logs = atoi(p);
+
+ lws_set_log_level(logs, NULL);
+ lwsl_user("LWS minimal ws server + threadpool | visit http://localhost:7681\n");
+
+ memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+ info.port = 7681;
+ info.mounts = &mount;
+ info.protocols = protocols;
+ info.pvo = &pvo; /* per-vhost options */
+
+ context = lws_create_context(&info);
+ if (!context) {
+ lwsl_err("lws init failed\n");
+ return 1;
+ }
+
+ /* start the threads that create content */
+
+ while (!interrupted)
+ if (lws_service(context, 1000))
+ interrupted = 1;
+
+ lws_context_destroy(context);
+
+ return 0;
+}
diff --git a/minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/favicon.ico b/minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/favicon.ico
new file mode 100644
index 000000000..c0cc2e3df
Binary files /dev/null and b/minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/favicon.ico differ
diff --git a/minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/index.html b/minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/index.html
new file mode 100644
index 000000000..47fb96b7f
--- /dev/null
+++ b/minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/index.html
@@ -0,0 +1,97 @@
+
+
+
+ 
+
+ Minimal ws server threadpool example.
+ 8 x ws connections are opened back to the example server.
+ There are three threads in the pool to service them, the
+ remainder are queued until a thread in the pool is free.
+ The textarea show the last 50 lines received.
+
+
+
+
+
+
+
+
+
diff --git a/minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/libwebsockets.org-logo.png b/minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/libwebsockets.org-logo.png
new file mode 100644
index 000000000..2060a10c9
Binary files /dev/null and b/minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/libwebsockets.org-logo.png differ
diff --git a/minimal-examples/ws-server/minimal-ws-server-threadpool/protocol_lws_minimal_threadpool.c b/minimal-examples/ws-server/minimal-ws-server-threadpool/protocol_lws_minimal_threadpool.c
new file mode 100644
index 000000000..c03888987
--- /dev/null
+++ b/minimal-examples/ws-server/minimal-ws-server-threadpool/protocol_lws_minimal_threadpool.c
@@ -0,0 +1,343 @@
+/*
+ * ws protocol handler plugin for "lws-minimal" demonstrating lws threadpool
+ *
+ * Copyright (C) 2010-2018 Andy Green
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * The main reason some things are as they are is that the task lifecycle may
+ * be unrelated to the wsi lifecycle that queued that task.
+ *
+ * Consider the task may call an external library and run for 30s without
+ * "checking in" to see if it should stop. The wsi that started the task may
+ * have closed at any time before the 30s are up, with the browser window
+ * closing or whatever.
+ *
+ * So data shared between the asynchronous task and the wsi must have its
+ * lifecycle determined by the task, not the wsi. That means a separate struct
+ * that can be freed by the task.
+ *
+ * In the case the wsi outlives the task, the tasks do not get destroyed until
+ * the service thread has called lws_threadpool_task_status() on the completed
+ * task. So there is no danger of the shared task private data getting randomly
+ * freed.
+ */
+
+#if !defined (LWS_PLUGIN_STATIC)
+#define LWS_DLL
+#define LWS_INTERNAL
+#include
+#endif
+
+#include
+
+struct per_vhost_data__minimal {
+ struct lws_threadpool *tp;
+ const char *config;
+};
+
+struct task_data {
+ char result[64];
+
+ uint64_t pos, end;
+};
+
+/*
+ * Create the private data for the task
+ *
+ * Notice we hand over responsibility for the cleanup and freeing of the
+ * allocated task_data to the threadpool, because the wsi it was originally
+ * bound to may close while the thread is still running. So we allocate
+ * something discrete for the task private data that can be definitively owned
+ * and freed by the threadpool, not the wsi... the pss won't do, as it only
+ * exists for the lifecycle of the wsi connection.
+ *
+ * When the task is created, we also tell it how to destroy the private data
+ * by giving it args.cleanup as cleanup_task_private_data() defined below.
+ */
+
+static struct task_data *
+create_task_private_data(void)
+{
+ struct task_data *priv = malloc(sizeof(*priv));
+
+ return priv;
+}
+
+/*
+ * Destroy the private data for the task
+ *
+ * Notice the wsi the task was originally bound to may be long gone, in the
+ * case we are destroying the lws context and the thread was doing something
+ * for a long time without checking in.
+ */
+static void
+cleanup_task_private_data(struct lws *wsi, void *user)
+{
+ struct task_data *priv = (struct task_data *)user;
+
+ free(priv);
+}
+
+/*
+ * This runs in its own thread, from the threadpool.
+ *
+ * The implementation behind this in lws uses pthreads, but no pthreadisms are
+ * required in the user code.
+ *
+ * The example counts to 10M, "checking in" to see if it should stop after every
+ * 100K and pausing to sync with the service thread to send a ws message every
+ * 1M. It resumes after the service thread determines the wsi is writable and
+ * the LWS_CALLBACK_SERVER_WRITEABLE indicates the task thread can continue by
+ * calling lws_threadpool_task_sync().
+ */
+
+static enum lws_threadpool_task_return
+task_function(void *user, enum lws_threadpool_task_status s)
+{
+ struct task_data *priv = (struct task_data *)user;
+ int budget = 100 * 1000;
+
+ if (priv->pos == priv->end)
+ return LWS_TP_RETURN_FINISHED;
+
+ /*
+ * Preferably replace this with ~100ms of your real task, so it
+ * can "check in" at short intervals to see if it has been asked to
+ * stop.
+ *
+ * You can just run tasks atomically here with the thread dedicated
+ * to it, but it will cause odd delays while shutting down etc and
+ * the task will run to completion even if the wsi that started it
+ * has since closed.
+ */
+
+ while (budget--)
+ priv->pos++;
+
+ usleep(100000);
+
+ if (!(priv->pos % (1000 * 1000))) {
+ lws_snprintf(priv->result + LWS_PRE,
+ sizeof(priv->result) - LWS_PRE,
+ "pos %llu", (unsigned long long)priv->pos);
+
+ return LWS_TP_RETURN_SYNC;
+ }
+
+ return LWS_TP_RETURN_CHECKING_IN;
+}
+
+static int
+callback_minimal(struct lws *wsi, enum lws_callback_reasons reason,
+ void *user, void *in, size_t len)
+{
+ struct per_vhost_data__minimal *vhd =
+ (struct per_vhost_data__minimal *)
+ lws_protocol_vh_priv_get(lws_get_vhost(wsi),
+ lws_get_protocol(wsi));
+ const struct lws_protocol_vhost_options *pvo;
+ struct lws_threadpool_create_args cargs;
+ struct lws_threadpool_task_args args;
+ struct lws_threadpool_task *task;
+ struct task_data *priv;
+ int n, m, r = 0;
+ char name[32];
+ void *_user;
+
+ switch (reason) {
+ case LWS_CALLBACK_PROTOCOL_INIT:
+ /* create our per-vhost struct */
+ vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
+ lws_get_protocol(wsi),
+ sizeof(struct per_vhost_data__minimal));
+ if (!vhd)
+ return 1;
+
+ /* recover the pointer to the globals struct */
+ pvo = lws_pvo_search(
+ (const struct lws_protocol_vhost_options *)in,
+ "config");
+ if (!pvo || !pvo->value) {
+ lwsl_err("%s: Can't find \"config\" pvo\n", __func__);
+ return 1;
+ }
+ vhd->config = pvo->value;
+
+ memset(&cargs, 0, sizeof(cargs));
+
+ cargs.max_queue_depth = 8;
+ cargs.threads = 3;
+ vhd->tp = lws_threadpool_create(lws_get_context(wsi),
+ &cargs, "%s",
+ lws_get_vhost_name(lws_get_vhost(wsi)));
+ if (!vhd->tp)
+ return 1;
+
+ lws_timed_callback_vh_protocol(lws_get_vhost(wsi),
+ lws_get_protocol(wsi),
+ LWS_CALLBACK_USER, 1);
+
+ break;
+
+ case LWS_CALLBACK_PROTOCOL_DESTROY:
+ lws_threadpool_finish(vhd->tp);
+ lws_threadpool_destroy(vhd->tp);
+ break;
+
+ case LWS_CALLBACK_USER:
+
+ /*
+ * in debug mode, dump the threadpool stat to the logs once
+ * a second
+ */
+ lws_threadpool_dump(vhd->tp);
+ lws_timed_callback_vh_protocol(lws_get_vhost(wsi),
+ lws_get_protocol(wsi),
+ LWS_CALLBACK_USER, 1);
+ break;
+
+ case LWS_CALLBACK_ESTABLISHED:
+
+ memset(&args, 0, sizeof(args));
+ priv = args.user = create_task_private_data();
+ if (!args.user)
+ return 1;
+
+ priv->pos = 0;
+ priv->end = 10 * 1000 * 1000;
+
+ /* queue the task... the task takes on responsibility for
+ * destroying args.user. pss->priv just has a copy of it */
+
+ args.wsi = wsi;
+ args.task = task_function;
+ args.cleanup = cleanup_task_private_data;
+
+ lws_get_peer_simple(wsi, name, sizeof(name));
+
+ if (!lws_threadpool_enqueue(vhd->tp, &args, "ws %s", name)) {
+ lwsl_user("%s: Couldn't enqueue task\n", __func__);
+ cleanup_task_private_data(wsi, priv);
+ return 1;
+ }
+
+ lws_set_timeout(wsi, PENDING_TIMEOUT_THREADPOOL, 30);
+
+ /*
+ * so the asynchronous worker will let us know the next step
+ * by causing LWS_CALLBACK_SERVER_WRITEABLE
+ */
+
+ break;
+
+ case LWS_CALLBACK_CLOSED:
+ break;
+
+ case LWS_CALLBACK_WS_SERVER_DROP_PROTOCOL:
+ lwsl_debug("LWS_CALLBACK_WS_SERVER_DROP_PROTOCOL: %p\n", wsi);
+ lws_threadpool_dequeue(wsi);
+ break;
+
+ case LWS_CALLBACK_SERVER_WRITEABLE:
+
+ /*
+ * even completed tasks wait in a queue until we call the
+ * below on them. Then they may destroy themselves and their
+ * args.user data (by calling the cleanup callback).
+ *
+ * If you need to get things from the still-valid private task
+ * data, copy it here before calling
+ * lws_threadpool_task_status() that may free the task and the
+ * private task data.
+ */
+
+ n = lws_threadpool_task_status_wsi(wsi, &task, &_user);
+ lwsl_debug("%s: LWS_CALLBACK_SERVER_WRITEABLE: status %d\n",
+ __func__, n);
+ switch(n) {
+
+ case LWS_TP_STATUS_FINISHED:
+ case LWS_TP_STATUS_STOPPED:
+ case LWS_TP_STATUS_QUEUED:
+ case LWS_TP_STATUS_RUNNING:
+ case LWS_TP_STATUS_STOPPING:
+ return 0;
+
+ case LWS_TP_STATUS_SYNCING:
+ /* the task has paused for us to do something */
+ break;
+ default:
+ return -1;
+ }
+
+ priv = (struct task_data *)_user;
+
+ lws_set_timeout(wsi, PENDING_TIMEOUT_THREADPOOL_TASK, 5);
+
+ n = strlen(priv->result + LWS_PRE);
+ m = lws_write(wsi, (unsigned char *)priv->result + LWS_PRE,
+ n, LWS_WRITE_TEXT);
+ if (m < n) {
+ lwsl_err("ERROR %d writing to ws socket\n", m);
+ lws_threadpool_task_sync(task, 1);
+ return -1;
+ }
+
+ /*
+ * service thread has done whatever it wanted to do with the
+ * data the task produced: if it's waiting to do more it can
+ * continue now.
+ */
+ lws_threadpool_task_sync(task, 0);
+ break;
+
+ default:
+ break;
+ }
+
+ return r;
+}
+
+#define LWS_PLUGIN_PROTOCOL_MINIMAL \
+ { \
+ "lws-minimal", \
+ callback_minimal, \
+ 0, \
+ 128, \
+ 0, NULL, 0 \
+ }
+
+#if !defined (LWS_PLUGIN_STATIC)
+
+/* boilerplate needed if we are built as a dynamic plugin */
+
+static const struct lws_protocols protocols[] = {
+ LWS_PLUGIN_PROTOCOL_MINIMAL
+};
+
+LWS_EXTERN LWS_VISIBLE int
+init_protocol_minimal(struct lws_context *context,
+ struct lws_plugin_capability *c)
+{
+ if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
+ lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
+ c->api_magic);
+ return 1;
+ }
+
+ c->protocols = protocols;
+ c->count_protocols = LWS_ARRAY_SIZE(protocols);
+ c->extensions = NULL;
+ c->count_extensions = 0;
+
+ return 0;
+}
+
+LWS_EXTERN LWS_VISIBLE int
+destroy_protocol_minimal(struct lws_context *context)
+{
+ return 0;
+}
+#endif