commit 8dbbdd77feaa5180b81e7682d2fa9c201c9a4f98
parent 6418a0b3b5a0f985933ab3e1789777d5fa3d5f1f
Author: Christian Grothoff <christian@grothoff.org>
Date: Tue, 28 Jul 2026 22:47:16 +0200
add tutorial for #5657
Diffstat:
9 files changed, 2441 insertions(+), 5 deletions(-)
diff --git a/doc/Makefile.am b/doc/Makefile.am
@@ -22,6 +22,7 @@ microhttpd_TEXINFOS = \
gpl-2.0.texi \
fdl-1.3.texi
microhttpd_tutorial_TEXINFOS = \
+ chapters/asyncresponses.inc \
chapters/basicauthentication.inc \
chapters/bibliography.inc \
chapters/callbackresponse.inc \
diff --git a/doc/chapters/asyncresponses.inc b/doc/chapters/asyncresponses.inc
@@ -0,0 +1,501 @@
+The previous chapter left one question open. A content reader that
+has no data ready may return @code{0}, but doing so turns the daemon
+into a busy loop, and the honest answer---suspend the connection---was
+only sketched there. This chapter builds the thing itself: a server
+whose slow work happens in threads of its own, whose connections are
+parked while that work is under way, and which is driven by an
+external @code{select()} loop so that the application stays in charge
+of its own event handling.
+
+The combination is a common one. Most requests are cheap and can be
+answered on the spot; a few of them start a report, a backup or a
+query that runs for seconds. Handling the slow ones inline would
+block every other client for as long as they take. Giving every
+connection its own thread---@code{MHD_USE_THREAD_PER_CONNECTION}---is
+no answer either, both because it scales badly and because suspending
+is not available in that mode at all. What is wanted is a single
+event loop that never blocks, plus a worker per slow job.
+
+
+@heading Who may call what
+
+Almost every difficulty in this design comes from one rule, so it is
+worth stating before any code:
+
+@table @asis
+@item @code{MHD_queue_response()}
+Must be called from the thread that runs the daemon, from inside the
+access handler. A worker thread must never queue a response.
+
+@item @code{MHD_suspend_connection()}
+May only be called from the access handler or from a content reader
+callback---that is, from @emph{MHD}'s own thread, while @emph{MHD} is
+calling into the application.
+
+@item @code{MHD_resume_connection()}
+May be called from any thread at any time, and this is the exception
+that makes the whole design work. It is the only @emph{MHD} function
+a worker thread ever needs.
+@end table
+
+@noindent
+So the split is: the worker computes and, when it has something,
+resumes. Everything that touches the connection or the response
+happens on the daemon's thread, where @emph{MHD} calls the application
+back. The worker and the daemon share exactly one object, a
+per-request context, and the only thing the worker does with the
+connection handle it holds is pass it to
+@code{MHD_resume_connection()}.
+
+The daemon has to be started with @code{MHD_ALLOW_SUSPEND_RESUME}; both
+suspend and resume call @code{MHD_PANIC()} and abort the process
+otherwise. Note also that suspending does not work with a thread pool:
+@code{MHD_resume_connection()} asserts that the daemon has none.
+
+
+@heading The event loop
+
+Leaving out @code{MHD_USE_INTERNAL_POLLING_THREAD} puts @emph{MHD}
+into external polling mode: it starts no thread of its own and does
+nothing until the application asks it to. The loop is the usual one,
+and the example adds its own descriptor to the very same sets---there
+is nothing special about @emph{MHD}'s:
+
+@verbatim
+ FD_ZERO (&rs); FD_ZERO (&ws); FD_ZERO (&es);
+ max = 0;
+ if (MHD_YES != MHD_get_fdset (daemon, &rs, &ws, &es, &max))
+ break; /* fatal internal error */
+ FD_SET (wake_pipe[0], &rs); /* our own wakeup pipe */
+ if (max < wake_pipe[0])
+ max = wake_pipe[0];
+
+ if (MHD_YES == MHD_get_timeout64 (daemon, &mhd_timeout))
+ { ...fill tv...; tvp = &tv; }
+ else
+ tvp = NULL; /* nothing to wait for */
+
+ select ((int) max + 1, &rs, &ws, &es, tvp);
+ MHD_run_from_select (daemon, &rs, &ws, &es);
+@end verbatim
+@noindent
+
+The interesting branch is the one that sets @code{tvp} to @code{NULL}.
+@code{MHD_get_timeout64()} returns @code{MHD_NO} when @emph{MHD} has
+nothing whatsoever to wait for, and with every connection suspended
+that is precisely the situation: suspended connections do not time
+out, so there is no deadline to wake up for. The loop then blocks in
+@code{select()} indefinitely. Whether the server works at all depends
+on something waking it up.
+
+
+@heading Waking the loop up
+
+That something is @emph{MHD}'s inter-thread communication channel.
+@code{MHD_ALLOW_SUSPEND_RESUME} is defined as
+@code{8192 | MHD_USE_ITC}, so requesting suspend support requests the
+ITC as well; @code{MHD_get_fdset()} puts the reading end of that
+channel into the read set, ahead of everything else; and
+@code{MHD_resume_connection()} writes a byte to it. A worker thread
+that resumes a connection therefore breaks the main thread out of
+@code{select()}, even though the two share nothing but the daemon
+handle.
+
+This deserves emphasis because the header file is misleading about it:
+the comment on @code{MHD_USE_ITC} says the flag is ignored with
+external polling. That is true for the flag in isolation, but not for
+the channel: @code{MHD_ALLOW_SUSPEND_RESUME} creates it regardless of
+the polling mode, and in external mode it is the application's own
+@code{select()} that watches it. Without that, a resumed connection
+would sit untouched until some unrelated event happened to wake the
+loop.
+
+There is no race to guard against here. If the worker resumes in the
+window between @code{MHD_get_fdset()} and @code{select()}, the byte is
+already in the channel when @code{select()} is entered, and
+@code{select()} returns immediately---the channel is level triggered,
+so a wakeup cannot be missed. The same property makes it safe for a
+worker to resume a connection that has not finished suspending yet:
+@emph{MHD} remembers the request and cancels the pending suspension
+instead of parking the connection. Neither side needs to know what
+the other is doing.
+
+
+@heading Answering in one piece
+
+The simplest of the two patterns is for an answer that is only useful
+as a whole---a generated report, the result of a query. Nothing can be
+sent before it is finished, so the connection is suspended in the
+access handler and the response is queued when @emph{MHD} comes back:
+
+@verbatim
+static enum MHD_Result
+handle_slow (struct MHD_Connection *connection, void **req_cls)
+{
+ struct Job *job = *req_cls;
+
+ if (NULL == job)
+ {
+ /* First call for this request: start the work and ask MHD to
+ come back to us. */
+ job = job_create (connection, 0);
+ if (NULL == job)
+ return MHD_NO;
+ if (MHD_NO == job_start (job))
+ { job_unref (job); job_unref (job); return MHD_NO; }
+ *req_cls = job;
+ return MHD_YES;
+ }
+ pthread_mutex_lock (&job->lock);
+ if (0 == job->finished)
+ {
+ job->suspended = 1;
+ MHD_suspend_connection (connection);
+ pthread_mutex_unlock (&job->lock);
+ return MHD_YES;
+ }
+ ...copy the result out from under the lock...
+ pthread_mutex_unlock (&job->lock);
+ ...MHD_create_response_from_buffer_copy() and MHD_queue_response()...
+}
+@end verbatim
+@noindent
+
+The control flow is worth following, because it is not obvious that it
+terminates. @emph{MHD} calls the access handler for a request more
+than once; the first call is the one where @code{*req_cls} is still
+@code{NULL}, and returning @code{MHD_YES} without queueing anything
+tells @emph{MHD} to carry on. On the second call the worker is
+running but not finished, so the connection is suspended and the
+handler returns again. When the worker resumes the connection,
+@emph{MHD} calls the handler a third time---on its own thread, with
+the same @code{*req_cls}---and this time @code{finished} is set and the
+response is queued.
+
+That third call is the answer to the question this pattern exists for.
+The worker never queues anything; it merely makes the daemon call the
+application back at a moment when the answer is ready.
+
+
+@heading Answering as it is produced
+
+The second pattern is the more useful one when the answer is long, and
+it is what makes a browser show results as they appear rather than
+after a long blank pause. Here the response is queued
+@emph{immediately}, with a content reader and an unknown size, so the
+headers go out at once and the body follows as the worker fills it in:
+
+@verbatim
+ job = job_create (connection, 1);
+ response = MHD_create_response_from_callback (MHD_SIZE_UNKNOWN,
+ IO_BLOCK_SIZE,
+ &stream_reader,
+ job,
+ &stream_done);
+ ...
+ MHD_add_response_header (response, MHD_HTTP_HEADER_CONTENT_TYPE,
+ "text/event-stream");
+ ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
+ MHD_destroy_response (response);
+@end verbatim
+@noindent
+
+Because the size is unknown, @emph{MHD} uses chunked transfer encoding,
+and every piece the content reader hands back is framed as its own
+chunk and goes out on the wire straight away. The content reader is
+where suspending happens:
+
+@verbatim
+static ssize_t
+stream_reader (void *cls, uint64_t pos, char *buf, size_t max)
+{
+ struct Job *job = cls;
+ size_t ready;
+
+ pthread_mutex_lock (&job->lock);
+ if (job->off == job->fill)
+ {
+ job->off = 0;
+ job->fill = 0;
+ if (0 != job->finished)
+ {
+ pthread_mutex_unlock (&job->lock);
+ return MHD_CONTENT_READER_END_OF_STREAM;
+ }
+ /* Nothing to send yet. Take the connection out of the event
+ loop; the worker will put it back in. */
+ job->suspended = 1;
+ MHD_suspend_connection (job->connection);
+ pthread_mutex_unlock (&job->lock);
+ return 0;
+ }
+ ready = job->fill - job->off;
+ if (ready > max)
+ ready = max;
+ memcpy (buf, &job->payload[job->off], ready);
+ job->off += ready;
+ pthread_mutex_unlock (&job->lock);
+ return (ssize_t) ready;
+}
+@end verbatim
+@noindent
+
+The return value is still @code{0}, exactly as in the busy-waiting
+version the previous chapter warned about. The difference is the line
+above it. Suspending removes the connection from the event loop, so
+@emph{MHD} has no reason to come back until the worker says so, and
+the process goes to sleep. The example ships with a test that measures
+this: streaming a document that takes one second to produce costs the
+server no measurable CPU time at all while the connection is suspended,
+and very nearly a full second---one core, saturated---when the
+@code{MHD_suspend_connection()} call is removed and only the
+@code{return 0} is left. The bytes that arrive are identical in both
+cases, which is what makes this such an easy mistake to keep.
+
+
+@heading The shared state
+
+Everything the two threads share sits in one structure per request:
+
+@verbatim
+struct Job
+{
+ pthread_mutex_t lock;
+ pthread_cond_t cond;
+ struct MHD_Connection *connection;
+ char payload[MAX_PAYLOAD];
+ size_t fill;
+ size_t off;
+ unsigned int rc;
+ int finished;
+ int suspended;
+ int abandoned;
+ ...
+};
+@end verbatim
+@noindent
+
+There is no registry of pending requests involved in answering them,
+and no lookup: the pointer travels in @code{*req_cls} for the first
+pattern and in the content reader's @code{crc_cls} for the second, and
+both the daemon and the worker are handed it directly. A shared list
+of all jobs is still useful, but only for shutting down in an orderly
+fashion, and it is protected by a lock of its own that is always taken
+before any job's lock, never after.
+
+The structure is reference counted, with one reference held by
+@emph{MHD} and one by the worker. The @emph{MHD} side lets go in the
+free callback of the response for a streamed answer, and in the
+@code{MHD_OPTION_NOTIFY_COMPLETED} callback for the other pattern; the
+worker lets go when it returns. Dropping the last reference does not
+free the job, though, because the worker still has to be joined and
+only the main thread may do that. The main loop therefore ends with a
+sweep that joins and frees whatever has no references left.
+
+
+@heading When the client goes away
+
+This is the part that is easy to get wrong, because it does not happen
+during development and does happen constantly in production. A client
+that closes the connection halfway through a download leaves a worker
+running with a pointer to a connection @emph{MHD} is about to free.
+
+The two callbacks that tell the application a request is over---the
+response's free callback and @code{MHD_OPTION_NOTIFY_COMPLETED}---are
+therefore where the worker is cut loose:
+
+@verbatim
+static void
+job_detach (struct Job *job)
+{
+ pthread_mutex_lock (&job->lock);
+ job->abandoned = 1;
+ job->connection = NULL;
+ pthread_cond_signal (&job->cond);
+ pthread_mutex_unlock (&job->lock);
+ job_unref (job);
+}
+@end verbatim
+@noindent
+
+and the worker checks that flag before every resume:
+
+@verbatim
+static void
+job_resume_locked (struct Job *job)
+{
+ if ( (0 == job->suspended) || (0 != job->abandoned) )
+ return;
+ job->suspended = 0;
+ MHD_resume_connection (job->connection);
+}
+@end verbatim
+@noindent
+
+Note that this function is called with the job's lock @emph{held}, and
+calls into @emph{MHD} without releasing it. That is deliberate and it
+is what makes the pattern safe. @emph{MHD} destroys the connection
+only after the completion callback has returned, and that callback
+cannot run while the worker holds the lock; so for as long as the
+worker is inside @code{MHD_resume_connection()}, the connection it
+passes is guaranteed to still exist. Dropping the lock first and
+resuming afterwards would open exactly the window this is meant to
+close.
+
+The reverse order never occurs: @emph{MHD} invokes the free and
+completion callbacks without holding the internal locks that
+@code{MHD_resume_connection()} acquires, so there is no way for the
+two to deadlock.
+
+The condition variable in the same function is a convenience rather
+than a necessity. Signalling it wakes a worker that is asleep between
+steps, so that the work stops within microseconds of the client
+disappearing instead of running to completion for nobody.
+
+
+@heading Shutting down
+
+Stopping a daemon that still has suspended connections is an API
+violation, and a suspended connection is one that only a worker can
+release. The order is therefore fixed:
+
+@enumerate
+@item
+Stop handing out new jobs.
+
+@item
+Tell every worker to give up, and @emph{join} them. After this point
+no connection can be suspended any more: the workers resume before
+they exit, and the content reader only ever suspends while a worker is
+running.
+
+@item
+Keep running the event loop until the connections have drained, so
+that the last bytes actually reach the clients.
+
+@item
+@code{MHD_stop_daemon()}.
+@end enumerate
+
+@noindent
+Skipping the join in step two is the tempting shortcut, and it is the
+one that leaves a connection parked forever with nothing left alive to
+wake it.
+
+
+@heading Example code
+
+The complete program is available as @code{asyncresponse.c}. It runs
+one external @code{select()} loop and serves four URLs:
+
+@table @code
+@item /
+A small page whose JavaScript opens the stream below and appends a row
+for every event as it arrives. The rows visibly trickle in, one per
+step, which is the most direct demonstration of what the chapter is
+about.
+
+@item /events
+The stream: a response is queued at once, and the content reader
+suspends between steps. The body is a sequence of server-sent events,
+which needs no library on the browser side.
+
+@item /slow
+The other pattern: the connection is suspended in the access handler
+and a complete answer is queued once the worker is done.
+
+@item /fast
+Answered on the spot. The page has a button that requests it and
+reports the round trip time; it stays in the low milliseconds while
+jobs are running, which is the point of suspending rather than
+blocking.
+@end table
+
+@noindent
+The program takes the port, the duration of a step in milliseconds and
+the number of steps on the command line. Passing @code{0} as the port
+makes the operating system choose a free one, which the program then
+reports via @code{MHD_DAEMON_INFO_BIND_PORT}:
+
+@verbatim
+$ ./asyncresponse 0 250 20
+Listening on port 44321
+@end verbatim
+@noindent
+
+The accompanying @code{test_asyncresponse.c} starts exactly that
+binary and checks over HTTP that @code{/slow} waits for its worker,
+that the body of @code{/events} really arrives in pieces spread over
+the lifetime of the request rather than in one burst at the end, that
+@code{/fast} is still served promptly while jobs are parked, that a
+client which hangs up in the middle takes neither the server nor the
+worker with it, and that the process exits cleanly when it is signalled
+while a connection is suspended. It also compares the server's CPU
+time against the wall clock time of a transfer, which is the only one
+of those checks that notices the difference between a suspended
+connection and a busy-waiting one.
+
+
+@heading Remarks
+
+@emph{MHD} does not detect that a client has disconnected while a
+connection is suspended, and the connection timeout does not run
+either. A suspended connection stays suspended until somebody resumes
+it, without exception. Any timeout on the work itself is therefore the
+application's responsibility.
+
+Suspended connections still count against the connection limits, both
+the global one and the per-IP one. A server that parks connections for
+minutes needs its limits sized for the number of jobs it expects to
+have in flight, not for the number it expects to be transmitting.
+
+If the client vanishes in the middle of a chunked response, the next
+write fails and @emph{MHD} logs it---with
+@code{MHD_USE_ERROR_LOG} enabled the example prints @code{Failed to
+send the chunked response body ... The socket is no longer available
+for sending}. That is the normal course of events for an aborted
+download and not a sign of a problem.
+
+Finally, none of this is specific to @code{select()}. The same
+argument holds for @code{poll()} and for @code{epoll}, where the
+daemon's descriptor can be obtained with
+@code{MHD_DAEMON_INFO_EPOLL_FD} and added to an epoll set of the
+application's own; @code{suspend_resume_epoll.c} among the
+distribution's examples shows that variant.
+
+
+@heading Exercises
+
+@itemize @bullet
+
+@item
+Give the jobs a deadline. Since a suspended connection never times
+out on its own, add a check to the main loop that resumes---and
+answers with @code{503}---any job that has been running for too long,
+and confirm with a step duration long enough to trigger it.
+
+@item
+Remove the @code{MHD_suspend_connection()} call from the content
+reader, leaving only the @code{return 0}, and watch the server process
+with @code{top} while a single client downloads the stream. Then put
+it back and watch again. Both servers send the same bytes.
+
+@item
+Take the resume out of the worker's final block, so that a finished job
+leaves its connection parked, and then stop the server while a job is
+in flight. Watch what the shutdown does---and what @code{valgrind}
+has to say about it---and work out which of the four steps above was
+violated.
+
+@item
+Replace the per-request thread with a fixed pool of worker threads and
+a queue of pending jobs. The reference counting and the
+@code{abandoned} flag should not need to change at all; if they do, the
+ownership rules were not as clean as they looked.
+
+@item
+Serve the stream to two browser windows at once and confirm from the
+timestamps that the two jobs run concurrently and are interleaved by a
+single-threaded event loop.
+
+@end itemize
diff --git a/doc/chapters/callbackresponse.inc b/doc/chapters/callbackresponse.inc
@@ -367,6 +367,12 @@ million times and burns a full core; the same callback suspending the
connection is entered exactly once more and the daemon uses no
measurable CPU time at all.
+Suspending brings requirements of its own---on who may call what from
+which thread, on how a worker thread wakes up a blocked event loop, and
+on who releases the shared state when a client disappears halfway
+through. @ref{Answering from another thread} works all of that out on
+a complete program.
+
@heading Trailers
@@ -456,7 +462,10 @@ handle in the context struct, and suspend the connection whenever the
buffer is empty. Do not forget @code{MHD_ALLOW_SUSPEND_RESUME} when
starting the daemon. Then remove the call to
@code{MHD_suspend_connection()}, leaving only the @code{return 0}, and
-compare the CPU usage of the server process.
+compare the CPU usage of the server process. A complete solution,
+together with the ownership rules that a shared buffer between a
+worker and the event loop needs, is the subject of
+@ref{Answering from another thread}.
@item
Compute a checksum over the generated document while it is being sent
diff --git a/doc/examples/.gitignore b/doc/examples/.gitignore
@@ -1,3 +1,5 @@
+/asyncresponse
+/test_asyncresponse
/upgrade
/tlsauthentication
/simplepost
@@ -12,3 +14,6 @@
*.o
*.lo
*.la
+# Produced by the automake test harness
+*.log
+*.trs
diff --git a/doc/examples/Makefile.am b/doc/examples/Makefile.am
@@ -3,7 +3,7 @@ SUBDIRS = .
AM_CPPFLAGS = \
-I$(top_srcdir)/src/include \
- $(CPPFLAGS_ac)
+ $(CPPFLAGS_ac) $(LIBCURL_CPPFLAGS)
AM_CFLAGS = $(CFLAGS_ac) @LIBGCRYPT_CFLAGS@
@@ -41,17 +41,44 @@ endif
endif
if HAVE_POSTPROCESSOR
-noinst_PROGRAMS += simplepost largepost sessions
+noinst_PROGRAMS += simplepost largepost sessions
+endif
+
+if HAVE_POSIX_THREADS
+noinst_PROGRAMS += asyncresponse
+if HAVE_FORK_WAITPID
+if RUN_LIBCURL_TESTS
+check_PROGRAMS = test_asyncresponse
+TESTS = $(check_PROGRAMS)
+endif
+endif
endif
if HAVE_W32
AM_CPPFLAGS += -DWINDOWS
endif
+asyncresponse_SOURCES = \
+ asyncresponse.c
+asyncresponse_CFLAGS = \
+ $(AM_CFLAGS) $(PTHREAD_CFLAGS)
+asyncresponse_LDADD = \
+ $(PTHREAD_LIBS) \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
+test_asyncresponse_SOURCES = \
+ test_asyncresponse.c
+test_asyncresponse_CFLAGS = \
+ $(AM_CFLAGS) $(PTHREAD_CFLAGS)
+test_asyncresponse_LDADD = \
+ $(PTHREAD_LIBS) \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la \
+ @LIBCURL@
+
basicauthentication_SOURCES = \
- basicauthentication.c
+ basicauthentication.c
basicauthentication_LDADD = \
- $(top_builddir)/src/microhttpd/libmicrohttpd.la
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
callbackresponse_SOURCES = \
callbackresponse.c
diff --git a/doc/examples/asyncresponse.c b/doc/examples/asyncresponse.c
@@ -0,0 +1,1193 @@
+/* Feel free to use this example code in any way
+ you see fit (Public Domain) */
+
+/**
+ * @file asyncresponse.c
+ * @brief Example for answering a request from another thread while
+ * the daemon is driven by an external select() loop. The
+ * main thread owns libmicrohttpd and does nothing but poll;
+ * every slow request gets a worker thread of its own and the
+ * connection is suspended for as long as that worker has
+ * nothing to say.
+ *
+ * "GET /" serves a small page whose JavaScript renders the
+ * data as it trickles in, "GET /events" is the stream behind
+ * it (a response is queued immediately and the content reader
+ * suspends between chunks), "GET /slow" suspends in the access
+ * handler until a complete answer is ready, and "GET /fast" is
+ * answered on the spot so that it can be used to show that the
+ * event loop never blocks.
+ *
+ * This example needs POSIX threads.
+ * @author Christian Grothoff
+ */
+
+#include <sys/types.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <microhttpd.h>
+#include <errno.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+/**
+ * Port to listen on if none was given on the command line. Pass 0 to
+ * let the operating system pick a free port; the port that was
+ * actually bound is then printed on standard output.
+ */
+#define DEFAULT_PORT 8888
+
+/**
+ * Time the worker thread pretends to need for one step, in
+ * milliseconds.
+ */
+#define DEFAULT_STEP_MS 250
+
+/**
+ * Number of steps that make up one job.
+ */
+#define DEFAULT_STEPS 20
+
+/**
+ * Size of the buffer in which a job collects the bytes that the
+ * content reader has not picked up yet.
+ */
+#define MAX_PAYLOAD 4096
+
+/**
+ * Block size we ask MHD to use when it queries our content reader.
+ */
+#define IO_BLOCK_SIZE 1024
+
+/**
+ * How long we are willing to wait for the last connections to go away
+ * when the server is shutting down, in milliseconds.
+ */
+#define DRAIN_TIMEOUT_MS 2000
+
+
+/**
+ * The front page. Its JavaScript opens the "/events" stream and adds
+ * a row for every server-sent event as it arrives, so that the delay
+ * between the steps is plainly visible. The "ping" button fires a
+ * request against "/fast" and reports the round trip time; it stays in
+ * the low milliseconds even while several jobs are running, which is
+ * the whole point of suspending instead of blocking.
+ */
+static const char *PAGE =
+ "<!DOCTYPE html>\n"
+ "<html lang='en'>\n"
+ "<head>\n"
+ "<meta charset='utf-8'>\n"
+ "<title>libmicrohttpd: answering from another thread</title>\n"
+ "<style>\n"
+ "body { font-family: sans-serif; max-width: 40em; margin: 2em auto; }\n"
+ "#bar { height: 1em; border: 1px solid #888; margin: 1em 0; }\n"
+ "#fill { height: 100%; width: 0; background: #4a90d9; transition: width .2s; }\n"
+ "#log { font-family: monospace; font-size: 90%; list-style: none; padding: 0; }\n"
+ "#log li { border-bottom: 1px solid #eee; padding: 2px 0; }\n"
+ "button { margin-right: .5em; }\n"
+ "</style>\n"
+ "</head>\n"
+ "<body>\n"
+ "<h1>Answering from another thread</h1>\n"
+ "<p>The server runs a single external <code>select()</code> loop. The job\n"
+ "below is computed by a thread of its own, and the connection carrying it is\n"
+ "suspended whenever that thread has nothing to say. Nothing is buffered: each\n"
+ "row appears the moment the worker produced it.</p>\n"
+ "<button id='go'>Start a job</button>\n"
+ "<button id='ping'>Ping /fast</button>\n"
+ "<div id='bar'><div id='fill'></div></div>\n"
+ "<p id='status'>idle</p>\n"
+ "<ul id='log'></ul>\n"
+ "<script>\n"
+ "const $ = (id) => document.getElementById(id);\n"
+ "let es = null;\n"
+ "let t0 = 0;\n"
+ "const stop = () => { if (es) { es.close(); es = null; } };\n"
+ "$('go').onclick = () => {\n"
+ " stop ();\n"
+ " $('log').innerHTML = '';\n"
+ " $('fill').style.width = '0';\n"
+ " $('status').textContent = 'running';\n"
+ " t0 = performance.now ();\n"
+ " es = new EventSource ('/events');\n"
+ " es.addEventListener ('step', (e) => {\n"
+ " const d = JSON.parse (e.data);\n"
+ " const li = document.createElement ('li');\n"
+ " li.textContent = '+' + Math.round (performance.now () - t0) +\n"
+ " ' ms ' + d.label;\n"
+ " $('log').appendChild (li);\n"
+ " $('fill').style.width = (100 * d.n / d.total) + '%';\n"
+ " });\n"
+ " es.addEventListener ('done', () => {\n"
+ " stop ();\n"
+ " $('status').textContent = 'done after ' +\n"
+ " Math.round (performance.now () - t0) + ' ms';\n"
+ " });\n"
+ " es.onerror = () => { stop (); $('status').textContent = 'connection lost'; };\n"
+ "};\n"
+ "$('ping').onclick = async () => {\n"
+ " const t = performance.now ();\n"
+ " await fetch ('/fast', { cache: 'no-store' });\n"
+ " $('status').textContent = '/fast answered in ' +\n"
+ " Math.round (performance.now () - t) + ' ms';\n"
+ "};\n"
+ "</script>\n"
+ "</body>\n"
+ "</html>\n";
+
+
+/**
+ * State shared between the thread that runs libmicrohttpd and the
+ * worker thread that produces the answer. Everything below @e lock is
+ * protected by it.
+ *
+ * The structure is reference counted, with one reference held by MHD
+ * (dropped in #stream_done() or #request_completed()) and one held by
+ * the worker (dropped when the worker function returns). Reaching zero
+ * does not free the job: only the main thread does that, in
+ * #reap_jobs(), because it is also the thread that has to join the
+ * worker.
+ */
+struct Job
+{
+ /**
+ * Kept in a doubly linked list of all jobs, so that the shutdown
+ * code can reach every worker. The list is protected by
+ * #jobs_lock, never by @e lock.
+ */
+ struct Job *next;
+
+ /**
+ * See @e next.
+ */
+ struct Job *prev;
+
+ /**
+ * Protects every field below, and---just as importantly---keeps the
+ * connection alive across #MHD_resume_connection().
+ */
+ pthread_mutex_t lock;
+
+ /**
+ * Used to wake the worker out of its sleep when the client goes away
+ * or the server is shutting down.
+ */
+ pthread_cond_t cond;
+
+ /**
+ * The worker thread. Only touched by the main thread, under
+ * #jobs_lock.
+ */
+ pthread_t tid;
+
+ /**
+ * The connection this job answers. Set to NULL by the MHD side as
+ * soon as MHD is done with the connection; the worker must not touch
+ * it after that, which is why @e abandoned is checked under @e lock
+ * before every #MHD_resume_connection().
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Bytes produced by the worker that the content reader has not
+ * picked up yet.
+ */
+ char payload[MAX_PAYLOAD];
+
+ /**
+ * Number of valid bytes in @e payload.
+ */
+ size_t fill;
+
+ /**
+ * Number of bytes of @e payload already handed to MHD.
+ */
+ size_t off;
+
+ /**
+ * Number of references, see the comment on the structure.
+ */
+ unsigned int rc;
+
+ /**
+ * Non-zero if the answer is streamed as it is produced ("/events"),
+ * zero if the client only ever sees the finished result ("/slow").
+ * A streaming job resumes the connection after every step, the other
+ * kind only once, at the very end.
+ */
+ int stream;
+
+ /**
+ * Non-zero once the worker has produced everything it is going to
+ * produce.
+ */
+ int finished;
+
+ /**
+ * Non-zero while the connection is suspended (or is just about to
+ * be, which under @e lock is the same thing).
+ */
+ int suspended;
+
+ /**
+ * Non-zero once MHD is done with the connection. The worker must
+ * not call any MHD function on @e connection any more.
+ */
+ int abandoned;
+
+ /**
+ * Non-zero if the worker should give up early.
+ */
+ int stop;
+
+ /**
+ * Non-zero once the worker thread was started. Main thread only.
+ */
+ int started;
+
+ /**
+ * Non-zero once the worker thread was joined. Main thread only.
+ */
+ int joined;
+};
+
+
+/**
+ * Head of the list of all jobs.
+ */
+static struct Job *jobs_head;
+
+/**
+ * Protects #jobs_head and the list links of every job. Lock order is
+ * #jobs_lock before any `struct Job`'s @e lock, never the other way
+ * round.
+ */
+static pthread_mutex_t jobs_lock = PTHREAD_MUTEX_INITIALIZER;
+
+/**
+ * Set by the signal handler, read by the main loop.
+ */
+static volatile sig_atomic_t shutdown_requested;
+
+/**
+ * Written to by the signal handler so that a blocking select() returns
+ * at once. MHD's own wakeup goes through its inter-thread
+ * communication channel, but our signal is our own business.
+ */
+static int wake_pipe[2] = { -1, -1 };
+
+/**
+ * Milliseconds the worker sleeps per step.
+ */
+static unsigned int step_ms = DEFAULT_STEP_MS;
+
+/**
+ * Number of steps that make up one job.
+ */
+static unsigned int total_steps = DEFAULT_STEPS;
+
+
+/**
+ * Signal handler for SIGINT and SIGTERM. Does the two things that are
+ * safe to do here: set a flag and poke a pipe.
+ *
+ * @param sig the signal that was received
+ */
+static void
+signal_handler (int sig)
+{
+ static const char c = 'x';
+
+ (void) sig; /* Unused. Silent compiler warning. */
+ shutdown_requested = 1;
+ if (0 > write (wake_pipe[1],
+ &c,
+ 1))
+ {
+ /* Nothing useful can be done about this here. */
+ }
+}
+
+
+/**
+ * Allocate a job and put it on the global list.
+ *
+ * @param connection the connection the job answers
+ * @param stream non-zero to deliver the steps as they are produced
+ * @return the new job with a reference count of two (one for MHD, one
+ * for the worker that the caller is about to start), NULL on
+ * error
+ */
+static struct Job *
+job_create (struct MHD_Connection *connection,
+ int stream)
+{
+ struct Job *job;
+
+ job = malloc (sizeof (struct Job));
+ if (NULL == job)
+ return NULL;
+ memset (job,
+ 0,
+ sizeof (struct Job));
+ if (0 != pthread_mutex_init (&job->lock,
+ NULL))
+ {
+ free (job);
+ return NULL;
+ }
+ if (0 != pthread_cond_init (&job->cond,
+ NULL))
+ {
+ pthread_mutex_destroy (&job->lock);
+ free (job);
+ return NULL;
+ }
+ job->connection = connection;
+ job->stream = stream;
+ job->rc = 2;
+ pthread_mutex_lock (&jobs_lock);
+ job->next = jobs_head;
+ if (NULL != jobs_head)
+ jobs_head->prev = job;
+ jobs_head = job;
+ pthread_mutex_unlock (&jobs_lock);
+ return job;
+}
+
+
+/**
+ * Drop a reference. Note that this never frees the job: the main
+ * thread has to join the worker first, and only it may do that. See
+ * #reap_jobs().
+ *
+ * @param job the job to release
+ */
+static void
+job_unref (struct Job *job)
+{
+ pthread_mutex_lock (&job->lock);
+ job->rc--;
+ pthread_mutex_unlock (&job->lock);
+}
+
+
+/**
+ * Called by the MHD side once MHD is done with the connection. After
+ * this returns, the worker will not touch the connection again.
+ *
+ * @param job the job to detach from its connection
+ */
+static void
+job_detach (struct Job *job)
+{
+ pthread_mutex_lock (&job->lock);
+ job->abandoned = 1;
+ job->connection = NULL;
+ /* Wake the worker out of its sleep: if the client hung up in the
+ middle of the transfer there is no point in producing the rest. */
+ pthread_cond_signal (&job->cond);
+ pthread_mutex_unlock (&job->lock);
+ job_unref (job);
+}
+
+
+/**
+ * Resume the connection of @a job if it is suspended.
+ *
+ * Must be called with @a job's lock held, and it deliberately calls
+ * into MHD while holding it: the MHD thread has to take the very same
+ * lock in #job_detach() before it can finish the connection, so as
+ * long as we hold the lock the connection cannot go away underneath
+ * us. The reverse order never occurs---MHD invokes our callbacks
+ * without holding any of its own locks that #MHD_resume_connection()
+ * would need---so this cannot deadlock.
+ *
+ * @param job the job whose connection to resume
+ */
+static void
+job_resume_locked (struct Job *job)
+{
+ if ( (0 == job->suspended) ||
+ (0 != job->abandoned) )
+ return;
+ job->suspended = 0;
+ MHD_resume_connection (job->connection);
+}
+
+
+/**
+ * Sleep for one step, or until the job is woken up.
+ *
+ * @param job the job to sleep on
+ * @return non-zero if the worker should stop early
+ */
+static int
+job_sleep (struct Job *job)
+{
+ struct timespec ts;
+ int stop;
+
+ if (0 != clock_gettime (CLOCK_REALTIME,
+ &ts))
+ return 1;
+ ts.tv_sec += (time_t) (step_ms / 1000);
+ ts.tv_nsec += (long) (step_ms % 1000) * 1000000L;
+ if (1000000000L <= ts.tv_nsec)
+ {
+ ts.tv_nsec -= 1000000000L;
+ ts.tv_sec++;
+ }
+ pthread_mutex_lock (&job->lock);
+ while ( (0 == job->stop) &&
+ (0 == job->abandoned) )
+ {
+ if (ETIMEDOUT == pthread_cond_timedwait (&job->cond,
+ &job->lock,
+ &ts))
+ break;
+ }
+ stop = (0 != job->stop) || (0 != job->abandoned);
+ pthread_mutex_unlock (&job->lock);
+ return stop;
+}
+
+
+/**
+ * Append @a len bytes from @a data to the job's payload buffer,
+ * compacting the buffer first. Must be called with @a job's lock
+ * held. A real application would need a policy for the case that the
+ * client cannot keep up; here we simply drop the step, which cannot
+ * happen with the sizes used in this example.
+ *
+ * @param job the job to append to
+ * @param data the bytes to append
+ * @param len the number of bytes to append
+ */
+static void
+job_append_locked (struct Job *job,
+ const char *data,
+ size_t len)
+{
+ if (0 != job->off)
+ {
+ memmove (job->payload,
+ &job->payload[job->off],
+ job->fill - job->off);
+ job->fill -= job->off;
+ job->off = 0;
+ }
+ if (len > MAX_PAYLOAD - job->fill)
+ return;
+ memcpy (&job->payload[job->fill],
+ data,
+ len);
+ job->fill += len;
+}
+
+
+/**
+ * The worker. This stands in for whatever expensive computation,
+ * database cursor or remote query a real application would run: it
+ * produces one server-sent event per step and resumes the connection
+ * whenever it has something new to offer.
+ *
+ * Note that the only MHD function this thread ever calls is
+ * #MHD_resume_connection(). Building and queueing the response is the
+ * business of the thread that runs the daemon.
+ *
+ * @param cls the `struct Job` to work on
+ * @return always NULL
+ */
+static void *
+worker (void *cls)
+{
+ struct Job *job = cls;
+ char event[256];
+ unsigned int i;
+ int len;
+
+ for (i = 1; i <= total_steps; i++)
+ {
+ if (0 != job_sleep (job))
+ break;
+ if (0 != job->stream)
+ len = snprintf (event,
+ sizeof (event),
+ "event: step\ndata: {\"n\":%u,\"total\":%u,"
+ "\"label\":\"step %u of %u done\"}\n\n",
+ i,
+ total_steps,
+ i,
+ total_steps);
+ else
+ len = snprintf (event,
+ sizeof (event),
+ "step %u of %u done\n",
+ i,
+ total_steps);
+ if ( (0 >= len) ||
+ (sizeof (event) <= (size_t) len) )
+ break;
+ pthread_mutex_lock (&job->lock);
+ job_append_locked (job,
+ event,
+ (size_t) len);
+ /* Only a streaming job has anything to show yet. The other kind
+ stays suspended until the very last step. */
+ if (0 != job->stream)
+ job_resume_locked (job);
+ pthread_mutex_unlock (&job->lock);
+ }
+ if (0 != job->stream)
+ len = snprintf (event,
+ sizeof (event),
+ "event: done\ndata: {\"n\":%u,\"total\":%u}\n\n",
+ total_steps,
+ total_steps);
+ else
+ len = snprintf (event,
+ sizeof (event),
+ "all %u steps done\n",
+ total_steps);
+ pthread_mutex_lock (&job->lock);
+ if ( (0 < len) &&
+ (sizeof (event) > (size_t) len) )
+ job_append_locked (job,
+ event,
+ (size_t) len);
+ job->finished = 1;
+ /* The last resume is not optional: if the connection were left
+ suspended, nothing would ever wake it up again, and the daemon
+ could not be stopped. */
+ job_resume_locked (job);
+ pthread_mutex_unlock (&job->lock);
+ job_unref (job);
+ return NULL;
+}
+
+
+/**
+ * Start the worker thread of @a job.
+ *
+ * @param job the job whose worker to start
+ * @return #MHD_YES on success
+ */
+static enum MHD_Result
+job_start (struct Job *job)
+{
+ pthread_mutex_lock (&jobs_lock);
+ if (0 != pthread_create (&job->tid,
+ NULL,
+ &worker,
+ job))
+ {
+ pthread_mutex_unlock (&jobs_lock);
+ return MHD_NO;
+ }
+ job->started = 1;
+ pthread_mutex_unlock (&jobs_lock);
+ return MHD_YES;
+}
+
+
+/**
+ * Content reader for the "/events" stream. This is the interesting
+ * one: when the worker has produced nothing since the last call we
+ * suspend the connection and return zero, instead of returning zero on
+ * its own---which would make MHD ask again immediately and burn a CPU
+ * core for the whole duration of the transfer.
+ *
+ * @param cls our `struct Job`
+ * @param pos number of bytes already returned for this response
+ * @param buf where to copy the data
+ * @param max maximum number of bytes to copy to @a buf
+ * @return number of bytes written to @a buf, 0 if the connection was
+ * suspended, MHD_CONTENT_READER_END_OF_STREAM at the end
+ */
+static ssize_t
+stream_reader (void *cls,
+ uint64_t pos,
+ char *buf,
+ size_t max)
+{
+ struct Job *job = cls;
+ size_t ready;
+
+ (void) pos; /* Unused. Silent compiler warning. */
+ pthread_mutex_lock (&job->lock);
+ if (job->off == job->fill)
+ {
+ job->off = 0;
+ job->fill = 0;
+ if (0 != job->finished)
+ {
+ pthread_mutex_unlock (&job->lock);
+ return MHD_CONTENT_READER_END_OF_STREAM;
+ }
+ /* Nothing to send yet. Take the connection out of the event loop;
+ the worker will put it back in. */
+ job->suspended = 1;
+ MHD_suspend_connection (job->connection);
+ pthread_mutex_unlock (&job->lock);
+ return 0;
+ }
+ ready = job->fill - job->off;
+ if (ready > max)
+ ready = max;
+ memcpy (buf,
+ &job->payload[job->off],
+ ready);
+ job->off += ready;
+ pthread_mutex_unlock (&job->lock);
+ return (ssize_t) ready;
+}
+
+
+/**
+ * Called by MHD when the response object of an "/events" request is
+ * destroyed, which happens for a completed transfer just as well as
+ * for a client that went away in the middle of one. This is the MHD
+ * side of the job's reference count.
+ *
+ * @param cls our `struct Job`
+ */
+static void
+stream_done (void *cls)
+{
+ job_detach (cls);
+}
+
+
+/**
+ * Handle "GET /events": queue a streaming response right away and let
+ * the content reader deal with the fact that the data does not exist
+ * yet.
+ *
+ * @param connection the connection to answer
+ * @return #MHD_YES on success
+ */
+static enum MHD_Result
+handle_events (struct MHD_Connection *connection)
+{
+ struct MHD_Response *response;
+ struct Job *job;
+ enum MHD_Result ret;
+
+ job = job_create (connection,
+ 1);
+ if (NULL == job)
+ return MHD_NO;
+ response = MHD_create_response_from_callback (MHD_SIZE_UNKNOWN,
+ IO_BLOCK_SIZE,
+ &stream_reader,
+ job,
+ &stream_done);
+ if (NULL == response)
+ {
+ /* No response object exists, so nobody will call stream_done() for
+ us; drop both references by hand. The worker was never
+ started. */
+ job_unref (job);
+ job_unref (job);
+ return MHD_NO;
+ }
+ if (MHD_NO == job_start (job))
+ {
+ MHD_destroy_response (response);
+ job_unref (job);
+ return MHD_NO;
+ }
+ (void) MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ "text/event-stream");
+ (void) MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CACHE_CONTROL,
+ "no-cache");
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_OK,
+ response);
+ MHD_destroy_response (response);
+ return ret;
+}
+
+
+/**
+ * Handle "GET /slow": the answer is only useful as a whole, so instead
+ * of streaming we suspend the connection until the worker is done.
+ * MHD then calls this function again---on its own thread---and that is
+ * where the response is queued. #MHD_queue_response() is never called
+ * from the worker.
+ *
+ * @param connection the connection to answer
+ * @param req_cls the per-request pointer, holding our `struct Job`
+ * @return #MHD_YES on success
+ */
+static enum MHD_Result
+handle_slow (struct MHD_Connection *connection,
+ void **req_cls)
+{
+ struct MHD_Response *response;
+ struct Job *job = *req_cls;
+ char answer[MAX_PAYLOAD];
+ size_t len;
+ enum MHD_Result ret;
+
+ if (NULL == job)
+ {
+ /* First call for this request: start the work and ask MHD to come
+ back to us. */
+ job = job_create (connection,
+ 0);
+ if (NULL == job)
+ return MHD_NO;
+ if (MHD_NO == job_start (job))
+ {
+ job_unref (job);
+ job_unref (job);
+ return MHD_NO;
+ }
+ *req_cls = job;
+ return MHD_YES;
+ }
+ pthread_mutex_lock (&job->lock);
+ if (0 == job->finished)
+ {
+ /* Still working. Park the connection; the worker resumes it when
+ it is done, and MHD will then enter this function once more. */
+ job->suspended = 1;
+ MHD_suspend_connection (connection);
+ pthread_mutex_unlock (&job->lock);
+ return MHD_YES;
+ }
+ len = job->fill - job->off;
+ if (len > sizeof (answer))
+ len = sizeof (answer);
+ memcpy (answer,
+ &job->payload[job->off],
+ len);
+ pthread_mutex_unlock (&job->lock);
+
+ response = MHD_create_response_from_buffer_copy (len,
+ answer);
+ if (NULL == response)
+ return MHD_NO;
+ (void) MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ "text/plain");
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_OK,
+ response);
+ MHD_destroy_response (response);
+ return ret;
+}
+
+
+/**
+ * Queue a complete response that is already sitting in memory.
+ *
+ * @param connection the connection to answer
+ * @param status the HTTP status code to use
+ * @param mime the value for the "Content-Type" header
+ * @param body the body to send
+ * @return #MHD_YES on success
+ */
+static enum MHD_Result
+queue_static (struct MHD_Connection *connection,
+ unsigned int status,
+ const char *mime,
+ const char *body)
+{
+ struct MHD_Response *response;
+ enum MHD_Result ret;
+
+ response = MHD_create_response_from_buffer_copy (strlen (body),
+ body);
+ if (NULL == response)
+ return MHD_NO;
+ (void) MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ mime);
+ (void) MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CACHE_CONTROL,
+ "no-store");
+ ret = MHD_queue_response (connection,
+ status,
+ response);
+ MHD_destroy_response (response);
+ return ret;
+}
+
+
+static enum MHD_Result
+answer_to_connection (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **req_cls)
+{
+ (void) cls; /* Unused. Silent compiler warning. */
+ (void) version; /* Unused. Silent compiler warning. */
+ (void) upload_data; /* Unused. Silent compiler warning. */
+ (void) upload_data_size; /* Unused. Silent compiler warning. */
+
+ if (0 != strcmp (method,
+ MHD_HTTP_METHOD_GET))
+ return MHD_NO;
+ if (0 == strcmp (url,
+ "/"))
+ return queue_static (connection,
+ MHD_HTTP_OK,
+ "text/html",
+ PAGE);
+ if (0 == strcmp (url,
+ "/fast"))
+ return queue_static (connection,
+ MHD_HTTP_OK,
+ "text/plain",
+ "pong\n");
+ if ( (0 == strcmp (url,
+ "/events")) ||
+ (0 == strcmp (url,
+ "/slow")) )
+ {
+ if (0 != shutdown_requested)
+ return queue_static (connection,
+ MHD_HTTP_SERVICE_UNAVAILABLE,
+ "text/plain",
+ "shutting down\n");
+ if (0 == strcmp (url,
+ "/events"))
+ return handle_events (connection);
+ return handle_slow (connection,
+ req_cls);
+ }
+ return queue_static (connection,
+ MHD_HTTP_NOT_FOUND,
+ "text/plain",
+ "not found\n");
+}
+
+
+/**
+ * Called by MHD when a request is done. For "/slow" this is where the
+ * MHD side of the reference count is dropped; the other handlers leave
+ * @a req_cls alone, so there is nothing to do for them.
+ *
+ * @param cls closure, unused
+ * @param connection the connection that finished
+ * @param req_cls the per-request pointer
+ * @param toe why the request ended
+ */
+static void
+request_completed (void *cls,
+ struct MHD_Connection *connection,
+ void **req_cls,
+ enum MHD_RequestTerminationCode toe)
+{
+ struct Job *job = *req_cls;
+
+ (void) cls; /* Unused. Silent compiler warning. */
+ (void) connection; /* Unused. Silent compiler warning. */
+ (void) toe; /* Unused. Silent compiler warning. */
+ if (NULL == job)
+ return;
+ *req_cls = NULL;
+ job_detach (job);
+}
+
+
+/**
+ * Free every job that nobody references any more, joining its worker
+ * on the way. Called from the main loop, and thus always from the
+ * thread that also runs the daemon.
+ */
+static void
+reap_jobs (void)
+{
+ struct Job *job;
+ struct Job *next;
+ unsigned int rc;
+
+ pthread_mutex_lock (&jobs_lock);
+ for (job = jobs_head; NULL != job; job = next)
+ {
+ next = job->next;
+ pthread_mutex_lock (&job->lock);
+ rc = job->rc;
+ pthread_mutex_unlock (&job->lock);
+ if (0 != rc)
+ continue;
+ if ( (0 != job->started) &&
+ (0 == job->joined) )
+ {
+ pthread_join (job->tid,
+ NULL);
+ job->joined = 1;
+ }
+ if (NULL != job->prev)
+ job->prev->next = job->next;
+ else
+ jobs_head = job->next;
+ if (NULL != job->next)
+ job->next->prev = job->prev;
+ pthread_cond_destroy (&job->cond);
+ pthread_mutex_destroy (&job->lock);
+ free (job);
+ }
+ pthread_mutex_unlock (&jobs_lock);
+}
+
+
+/**
+ * Ask every worker to stop and wait until they all did.
+ *
+ * This has to happen before the daemon is stopped: a worker that is
+ * still asleep may be holding the only promise to resume a suspended
+ * connection, and stopping a daemon that has suspended connections is
+ * an API violation. Once every worker has returned, no connection can
+ * be suspended any more, because the workers resume before they exit
+ * and the content reader only ever suspends while a worker is running.
+ */
+static void
+stop_all_jobs (void)
+{
+ struct Job *job;
+
+ pthread_mutex_lock (&jobs_lock);
+ for (job = jobs_head; NULL != job; job = job->next)
+ {
+ pthread_mutex_lock (&job->lock);
+ job->stop = 1;
+ pthread_cond_signal (&job->cond);
+ pthread_mutex_unlock (&job->lock);
+ }
+ for (job = jobs_head; NULL != job; job = job->next)
+ {
+ if ( (0 != job->started) &&
+ (0 == job->joined) )
+ {
+ pthread_join (job->tid,
+ NULL);
+ job->joined = 1;
+ }
+ }
+ pthread_mutex_unlock (&jobs_lock);
+}
+
+
+/**
+ * Number of connections the daemon is currently handling.
+ *
+ * @param daemon the daemon to query
+ * @return the number of connections, 0 if it cannot be determined
+ */
+static unsigned int
+connection_count (struct MHD_Daemon *daemon)
+{
+ const union MHD_DaemonInfo *info;
+
+ info = MHD_get_daemon_info (daemon,
+ MHD_DAEMON_INFO_CURRENT_CONNECTIONS);
+ if (NULL == info)
+ return 0;
+ return info->num_connections;
+}
+
+
+int
+main (int argc,
+ char *const *argv)
+{
+ struct MHD_Daemon *daemon;
+ const union MHD_DaemonInfo *info;
+ struct sigaction sa;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ struct timeval tv;
+ struct timeval *tvp;
+ MHD_socket max;
+ uint64_t mhd_timeout;
+ unsigned long port = DEFAULT_PORT;
+ unsigned int drained_ms = 0;
+ char drain_buf[64];
+ int draining = 0;
+ int ret = 0;
+
+ if (1 < argc)
+ port = strtoul (argv[1],
+ NULL,
+ 10);
+ if (2 < argc)
+ step_ms = (unsigned int) strtoul (argv[2],
+ NULL,
+ 10);
+ if (3 < argc)
+ total_steps = (unsigned int) strtoul (argv[3],
+ NULL,
+ 10);
+ if ( (65535 < port) ||
+ (0 == total_steps) )
+ {
+ fprintf (stderr,
+ "Usage: %s [PORT [STEP_MS [STEPS]]]\n"
+ "Pass 0 as PORT to let the system pick a free one.\n",
+ argv[0]);
+ return 1;
+ }
+
+ if (0 > pipe (wake_pipe))
+ {
+ fprintf (stderr,
+ "Failed to create wakeup pipe: %s\n",
+ strerror (errno));
+ return 1;
+ }
+ memset (&sa,
+ 0,
+ sizeof (sa));
+ sa.sa_handler = &signal_handler;
+ sigaction (SIGINT,
+ &sa,
+ NULL);
+ sigaction (SIGTERM,
+ &sa,
+ NULL);
+ sa.sa_handler = SIG_IGN;
+ sigaction (SIGPIPE,
+ &sa,
+ NULL);
+
+ /* No MHD_USE_INTERNAL_POLLING_THREAD: the loop below is ours.
+ MHD_ALLOW_SUSPEND_RESUME implies MHD_USE_ITC, and that channel is
+ what lets a worker thread break us out of the select() call. */
+ daemon = MHD_start_daemon (MHD_ALLOW_SUSPEND_RESUME | MHD_USE_ERROR_LOG,
+ (uint16_t) port,
+ NULL, NULL,
+ &answer_to_connection, NULL,
+ MHD_OPTION_NOTIFY_COMPLETED,
+ &request_completed, NULL,
+ MHD_OPTION_END);
+ if (NULL == daemon)
+ {
+ fprintf (stderr,
+ "Failed to start daemon.\n");
+ return 1;
+ }
+ info = MHD_get_daemon_info (daemon,
+ MHD_DAEMON_INFO_BIND_PORT);
+ if ( (NULL == info) ||
+ (0 == info->port) )
+ {
+ fprintf (stderr,
+ "Failed to determine bound port.\n");
+ MHD_stop_daemon (daemon);
+ return 1;
+ }
+ printf ("Listening on port %u\n",
+ (unsigned int) info->port);
+ fflush (stdout);
+
+ while (1)
+ {
+ if ( (0 != shutdown_requested) &&
+ (0 == draining) )
+ {
+ /* Every worker has to be gone before the daemon may be stopped. */
+ stop_all_jobs ();
+ draining = 1;
+ }
+ if ( (0 != draining) &&
+ ( (0 == connection_count (daemon)) ||
+ (DRAIN_TIMEOUT_MS <= drained_ms) ) )
+ break;
+
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ max = 0;
+ if (MHD_YES != MHD_get_fdset (daemon,
+ &rs,
+ &ws,
+ &es,
+ &max))
+ {
+ fprintf (stderr,
+ "Failed to obtain the file descriptor set.\n");
+ ret = 1;
+ break;
+ }
+ /* Our own descriptors go into the very same sets. */
+ FD_SET (wake_pipe[0],
+ &rs);
+ if (max < wake_pipe[0])
+ max = wake_pipe[0];
+
+ if (0 != draining)
+ {
+ /* Look at the drain deadline regularly. */
+ tv.tv_sec = 0;
+ tv.tv_usec = 50 * 1000;
+ tvp = &tv;
+ drained_ms += 50;
+ }
+ else if (MHD_YES == MHD_get_timeout64 (daemon,
+ &mhd_timeout))
+ {
+ tv.tv_sec = (time_t) (mhd_timeout / 1000);
+ tv.tv_usec = ((long) (mhd_timeout % 1000)) * 1000;
+ tvp = &tv;
+ }
+ else
+ {
+ /* MHD has nothing to wait for. With every connection suspended
+ this is the normal case, and the loop simply sleeps until a
+ worker resumes one---which reaches us through MHD's ITC, as it
+ is part of the read set above. */
+ tvp = NULL;
+ }
+
+ if (-1 == select ((int) max + 1,
+ &rs,
+ &ws,
+ &es,
+ tvp))
+ {
+ if (EINTR == errno)
+ continue;
+ fprintf (stderr,
+ "Aborting due to error during select: %s\n",
+ strerror (errno));
+ ret = 1;
+ break;
+ }
+ if (FD_ISSET (wake_pipe[0],
+ &rs))
+ (void) read (wake_pipe[0],
+ drain_buf,
+ sizeof (drain_buf));
+ MHD_run_from_select (daemon,
+ &rs,
+ &ws,
+ &es);
+ reap_jobs ();
+ }
+
+ stop_all_jobs ();
+ reap_jobs ();
+ MHD_stop_daemon (daemon);
+ close (wake_pipe[0]);
+ close (wake_pipe[1]);
+ return ret;
+}
diff --git a/doc/examples/test_asyncresponse.c b/doc/examples/test_asyncresponse.c
@@ -0,0 +1,671 @@
+/* Feel free to use this example code in any way
+ you see fit (Public Domain) */
+
+/**
+ * @file test_asyncresponse.c
+ * @brief Test for the "asyncresponse" tutorial example. The example
+ * is started as a child process on an ephemeral port and then
+ * exercised over HTTP, so that what is tested is exactly the
+ * code the tutorial prints.
+ *
+ * Beyond checking that the right bytes come back, the test
+ * verifies the two properties that the chapter is actually
+ * about: that the body of "/events" really trickles in rather
+ * than arriving in one piece at the end, and that the server
+ * burns no CPU while it waits. The latter is what tells a
+ * properly suspended connection apart from a content reader
+ * that returns zero in a loop---both deliver the same bytes.
+ * @author Christian Grothoff
+ */
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <curl/curl.h>
+#include <errno.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+/**
+ * Milliseconds the example should spend per step.
+ */
+#define STEP_MS 50
+
+/**
+ * Number of steps per job, and thus (STEPS * STEP_MS) milliseconds of
+ * work for one request.
+ */
+#define STEPS 20
+
+/**
+ * Nominal duration of one job in milliseconds.
+ */
+#define JOB_MS (STEPS * STEP_MS)
+
+/**
+ * Upper bound for any single request, in seconds.
+ */
+#define REQUEST_TIMEOUT 30
+
+/**
+ * Largest response body we are prepared to keep.
+ */
+#define MAX_BODY 65536
+
+
+/**
+ * What one HTTP request collected.
+ */
+struct Fetch
+{
+ /**
+ * The body, as far as it was received.
+ */
+ char body[MAX_BODY];
+
+ /**
+ * Number of valid bytes in @e body.
+ */
+ size_t len;
+
+ /**
+ * Number of times the write callback was invoked.
+ */
+ unsigned int chunks;
+
+ /**
+ * Time of the first invocation of the write callback, in
+ * milliseconds since the start of the request.
+ */
+ long first_ms;
+
+ /**
+ * Time of the last invocation of the write callback, in
+ * milliseconds since the start of the request.
+ */
+ long last_ms;
+
+ /**
+ * Start of the request.
+ */
+ struct timespec start;
+
+ /**
+ * Abort the transfer after this many callbacks; zero to never abort.
+ */
+ unsigned int abort_after;
+
+ /**
+ * Result of curl_easy_perform().
+ */
+ CURLcode res;
+};
+
+
+/**
+ * The example's process ID.
+ */
+static pid_t server_pid;
+
+/**
+ * The port the example bound.
+ */
+static unsigned int server_port;
+
+/**
+ * Number of checks that failed.
+ */
+static unsigned int failures;
+
+
+/**
+ * Report the outcome of one check.
+ *
+ * @param ok non-zero if the check passed
+ * @param what what was being checked
+ */
+static void
+check (int ok,
+ const char *what)
+{
+ if (! ok)
+ failures++;
+ fprintf (stderr,
+ "%s: %s\n",
+ ok ? "PASS" : "FAIL",
+ what);
+}
+
+
+/**
+ * @return the current value of the monotonic clock, in milliseconds
+ */
+static long
+now_ms (void)
+{
+ struct timespec ts;
+
+ if (0 != clock_gettime (CLOCK_MONOTONIC,
+ &ts))
+ return 0;
+ return (long) ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
+}
+
+
+/**
+ * @param since the earlier point in time
+ * @return milliseconds elapsed since @a since
+ */
+static long
+elapsed_ms (const struct timespec *since)
+{
+ struct timespec ts;
+
+ if (0 != clock_gettime (CLOCK_MONOTONIC,
+ &ts))
+ return 0;
+ return (long) (ts.tv_sec - since->tv_sec) * 1000
+ + (ts.tv_nsec - since->tv_nsec) / 1000000;
+}
+
+
+/**
+ * Read the CPU time consumed by process @a pid so far. Only
+ * implemented for Linux; everywhere else the test simply skips the
+ * check that uses it.
+ *
+ * @param pid the process to look at
+ * @param[out] ms where to store the sum of user and system time in
+ * milliseconds
+ * @return non-zero on success
+ */
+static int
+cpu_time_ms (pid_t pid,
+ long *ms)
+{
+#ifdef __linux__
+ char path[64];
+ char buf[1024];
+ char *p;
+ FILE *f;
+ size_t got;
+ unsigned long utime;
+ unsigned long stime;
+ long ticks;
+
+ snprintf (path,
+ sizeof (path),
+ "/proc/%ld/stat",
+ (long) pid);
+ f = fopen (path,
+ "r");
+ if (NULL == f)
+ return 0;
+ got = fread (buf,
+ 1,
+ sizeof (buf) - 1,
+ f);
+ fclose (f);
+ if (0 == got)
+ return 0;
+ buf[got] = '\0';
+ /* The second field is the executable name and may contain spaces and
+ parentheses, so start parsing behind its closing one. */
+ p = strrchr (buf,
+ ')');
+ if (NULL == p)
+ return 0;
+ if (2 != sscanf (p + 1,
+ " %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %lu %lu",
+ &utime,
+ &stime))
+ return 0;
+ ticks = sysconf (_SC_CLK_TCK);
+ if (0 >= ticks)
+ return 0;
+ *ms = (long) ((utime + stime) * 1000 / (unsigned long) ticks);
+ return 1;
+#else
+ (void) pid;
+ (void) ms;
+ return 0;
+#endif
+}
+
+
+static size_t
+write_cb (void *ptr,
+ size_t size,
+ size_t nmemb,
+ void *cls)
+{
+ struct Fetch *f = cls;
+ size_t total = size * nmemb;
+
+ f->chunks++;
+ if (1 == f->chunks)
+ f->first_ms = elapsed_ms (&f->start);
+ f->last_ms = elapsed_ms (&f->start);
+ if ( (0 != f->abort_after) &&
+ (f->chunks >= f->abort_after) )
+ return 0; /* makes curl abort the transfer */
+ if (total > sizeof (f->body) - f->len - 1)
+ total = sizeof (f->body) - f->len - 1;
+ memcpy (&f->body[f->len],
+ ptr,
+ total);
+ f->len += total;
+ f->body[f->len] = '\0';
+ return size * nmemb;
+}
+
+
+/**
+ * Perform one GET against the example.
+ *
+ * @param path the path to request
+ * @param[out] f where to store what was received; @e abort_after has to
+ * be set by the caller, everything else is initialised here
+ * @return milliseconds the request took
+ */
+static long
+fetch (const char *path,
+ struct Fetch *f)
+{
+ CURL *curl;
+ char url[128];
+ unsigned int abort_after = f->abort_after;
+
+ memset (f,
+ 0,
+ sizeof (struct Fetch));
+ f->abort_after = abort_after;
+ f->res = CURLE_FAILED_INIT;
+ snprintf (url,
+ sizeof (url),
+ "http://127.0.0.1:%u%s",
+ server_port,
+ path);
+ curl = curl_easy_init ();
+ if (NULL == curl)
+ return 0;
+ curl_easy_setopt (curl, CURLOPT_URL, url);
+ curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, &write_cb);
+ curl_easy_setopt (curl, CURLOPT_WRITEDATA, f);
+ curl_easy_setopt (curl, CURLOPT_TIMEOUT, (long) REQUEST_TIMEOUT);
+ curl_easy_setopt (curl, CURLOPT_NOSIGNAL, 1L);
+ clock_gettime (CLOCK_MONOTONIC,
+ &f->start);
+ f->res = curl_easy_perform (curl);
+ curl_easy_cleanup (curl);
+ return elapsed_ms (&f->start);
+}
+
+
+/**
+ * Thread body that streams "/events" in the background while the main
+ * thread does something else.
+ *
+ * @param cls a `struct Fetch` whose @e abort_after is already set
+ * @return always NULL
+ */
+static void *
+background_stream (void *cls)
+{
+ (void) fetch ("/events",
+ cls);
+ return NULL;
+}
+
+
+/**
+ * Start the example on an ephemeral port and learn which one it got.
+ *
+ * @return non-zero on success
+ */
+static int
+start_server (void)
+{
+ char steps[16];
+ char step_ms[16];
+ int fds[2];
+ FILE *out;
+ char line[128];
+
+ if (0 != pipe (fds))
+ return 0;
+ snprintf (step_ms,
+ sizeof (step_ms),
+ "%u",
+ (unsigned int) STEP_MS);
+ snprintf (steps,
+ sizeof (steps),
+ "%u",
+ (unsigned int) STEPS);
+ server_pid = fork ();
+ if (-1 == server_pid)
+ {
+ close (fds[0]);
+ close (fds[1]);
+ return 0;
+ }
+ if (0 == server_pid)
+ {
+ close (fds[0]);
+ if (STDOUT_FILENO != fds[1])
+ {
+ dup2 (fds[1],
+ STDOUT_FILENO);
+ close (fds[1]);
+ }
+ execl ("./asyncresponse",
+ "asyncresponse",
+ "0",
+ step_ms,
+ steps,
+ (char *) NULL);
+ fprintf (stderr,
+ "Failed to exec ./asyncresponse: %s\n",
+ strerror (errno));
+ _exit (77);
+ }
+ close (fds[1]);
+ out = fdopen (fds[0],
+ "r");
+ if (NULL == out)
+ {
+ close (fds[0]);
+ return 0;
+ }
+ if (NULL == fgets (line,
+ sizeof (line),
+ out))
+ {
+ fclose (out);
+ return 0;
+ }
+ fclose (out);
+ if (1 != sscanf (line,
+ "Listening on port %u",
+ &server_port))
+ {
+ fprintf (stderr,
+ "Unexpected greeting from the example: %s",
+ line);
+ return 0;
+ }
+ return 1;
+}
+
+
+/**
+ * "GET /slow" suspends in the access handler and is answered as a
+ * whole once the worker is done.
+ */
+static void
+test_slow (void)
+{
+ struct Fetch f;
+ long ms;
+
+ memset (&f,
+ 0,
+ sizeof (f));
+ ms = fetch ("/slow",
+ &f);
+ check (CURLE_OK == f.res,
+ "/slow completed");
+ check (NULL != strstr (f.body,
+ "all 20 steps done"),
+ "/slow delivered the complete answer");
+ /* The worker sleeps STEP_MS per step, so the answer cannot possibly
+ be ready earlier. Only a lower bound is checked: a loaded machine
+ may take arbitrarily longer. */
+ check (ms >= JOB_MS / 2,
+ "/slow waited for the worker");
+}
+
+
+/**
+ * "GET /events" streams as the worker produces, and the server stays
+ * idle in between. This is the check that a content reader returning
+ * zero in a loop would fail.
+ */
+static void
+test_events_are_incremental (void)
+{
+ struct Fetch f;
+ long cpu_before;
+ long cpu_after;
+ long ms;
+ int have_cpu;
+
+ memset (&f,
+ 0,
+ sizeof (f));
+ have_cpu = cpu_time_ms (server_pid,
+ &cpu_before);
+ ms = fetch ("/events",
+ &f);
+ check (CURLE_OK == f.res,
+ "/events completed");
+ check (NULL != strstr (f.body,
+ "event: done"),
+ "/events delivered the final event");
+ check (5 <= f.chunks,
+ "/events arrived in several pieces");
+ /* The decisive one: the first piece has to show up long before the
+ last. A response that is assembled first and sent afterwards would
+ have first_ms very close to last_ms. */
+ check (f.last_ms - f.first_ms >= JOB_MS / 2,
+ "/events trickled in rather than arriving at once");
+
+ if (! have_cpu)
+ {
+ fprintf (stderr,
+ "SKIP: no way to read the server's CPU time on this "
+ "platform\n");
+ return;
+ }
+ if (! cpu_time_ms (server_pid,
+ &cpu_after))
+ {
+ fprintf (stderr,
+ "SKIP: could not read the server's CPU time\n");
+ return;
+ }
+ /* The server spent almost all of that second waiting. If the
+ connection were not suspended, MHD would poll the content reader
+ as fast as it can and this would be close to 100% of one core. */
+ fprintf (stderr,
+ "server used %ld ms of CPU during %ld ms of streaming\n",
+ cpu_after - cpu_before,
+ ms);
+ check ((cpu_after - cpu_before) * 4 < ms,
+ "server stayed idle while the connection was suspended");
+}
+
+
+/**
+ * The event loop keeps serving other requests while jobs are parked.
+ */
+static void
+test_loop_stays_responsive (void)
+{
+ struct Fetch stream;
+ struct Fetch ping;
+ pthread_t tid;
+ long worst = 0;
+ long ms;
+ unsigned int i;
+
+ memset (&stream,
+ 0,
+ sizeof (stream));
+ if (0 != pthread_create (&tid,
+ NULL,
+ &background_stream,
+ &stream))
+ {
+ check (0,
+ "could not start the background stream");
+ return;
+ }
+ for (i = 0; i < 5; i++)
+ {
+ memset (&ping,
+ 0,
+ sizeof (ping));
+ ms = fetch ("/fast",
+ &ping);
+ if ( (CURLE_OK != ping.res) ||
+ (NULL == strstr (ping.body,
+ "pong")) )
+ worst = REQUEST_TIMEOUT * 1000;
+ else if (ms > worst)
+ worst = ms;
+ usleep (50 * 1000);
+ }
+ pthread_join (tid,
+ NULL);
+ fprintf (stderr,
+ "slowest /fast while a job was running: %ld ms\n",
+ worst);
+ check (worst < 500,
+ "/fast was served promptly while a job was suspended");
+}
+
+
+/**
+ * A client that goes away in the middle must not take the server with
+ * it, and the worker behind it has to be cleaned up.
+ */
+static void
+test_client_abort (void)
+{
+ struct Fetch f;
+ struct Fetch ping;
+
+ memset (&f,
+ 0,
+ sizeof (f));
+ f.abort_after = 3;
+ (void) fetch ("/events",
+ &f);
+ check (CURLE_OK != f.res,
+ "aborted transfer was reported as such");
+ memset (&ping,
+ 0,
+ sizeof (ping));
+ (void) fetch ("/fast",
+ &ping);
+ check ( (CURLE_OK == ping.res) &&
+ (NULL != strstr (ping.body,
+ "pong")),
+ "server survived a client that hung up mid-stream");
+}
+
+
+/**
+ * Stopping the daemon while a connection is suspended is an API
+ * violation, so the example has to resume everything first. If it got
+ * that wrong we would see a panic or a hang here instead of a clean
+ * exit.
+ */
+static void
+test_clean_shutdown (void)
+{
+ struct Fetch stream;
+ pthread_t tid;
+ int status;
+ pid_t got;
+
+ memset (&stream,
+ 0,
+ sizeof (stream));
+ if (0 != pthread_create (&tid,
+ NULL,
+ &background_stream,
+ &stream))
+ {
+ check (0,
+ "could not start the background stream");
+ return;
+ }
+ /* Give the job time to get going, so that its connection really is
+ suspended when the signal arrives. */
+ usleep ((useconds_t) (STEP_MS * 3) * 1000);
+ kill (server_pid,
+ SIGTERM);
+ pthread_join (tid,
+ NULL);
+ got = waitpid (server_pid,
+ &status,
+ 0);
+ server_pid = -1;
+ check (0 < got,
+ "reaped the server");
+ check (WIFEXITED (status) && (0 == WEXITSTATUS (status)),
+ "server shut down cleanly with a suspended connection");
+}
+
+
+int
+main (void)
+{
+ long deadline;
+
+ if (0 != curl_global_init (CURL_GLOBAL_ALL))
+ return 77;
+ if (! start_server ())
+ {
+ fprintf (stderr,
+ "Could not start ./asyncresponse\n");
+ curl_global_cleanup ();
+ return 77;
+ }
+ fprintf (stderr,
+ "example listening on port %u\n",
+ server_port);
+
+ /* Wait for the listen socket to be usable. */
+ deadline = now_ms () + 5000;
+ while (now_ms () < deadline)
+ {
+ struct Fetch f;
+
+ memset (&f,
+ 0,
+ sizeof (f));
+ (void) fetch ("/fast",
+ &f);
+ if (CURLE_OK == f.res)
+ break;
+ usleep (50 * 1000);
+ }
+
+ test_slow ();
+ test_events_are_incremental ();
+ test_loop_stays_responsive ();
+ test_client_abort ();
+ test_clean_shutdown ();
+
+ if (-1 != server_pid)
+ {
+ kill (server_pid,
+ SIGKILL);
+ waitpid (server_pid,
+ NULL,
+ 0);
+ }
+ curl_global_cleanup ();
+ if (0 != failures)
+ fprintf (stderr,
+ "%u check(s) failed\n",
+ failures);
+ return (0 == failures) ? 0 : 1;
+}
diff --git a/doc/libmicrohttpd-tutorial.texi b/doc/libmicrohttpd-tutorial.texi
@@ -68,6 +68,7 @@ Free Documentation License".
* Improved processing of POST data::
* Session management::
* Generating responses on the fly::
+* Answering from another thread::
* Adding a layer of security::
* Upgrading connections::
* Bibliography::
@@ -111,6 +112,10 @@ Free Documentation License".
@chapter Generating responses on the fly
@include chapters/callbackresponse.inc
+@node Answering from another thread
+@chapter Answering from another thread
+@include chapters/asyncresponses.inc
+
@node Adding a layer of security
@chapter Adding a layer of security
@include chapters/tlsauthentication.inc
@@ -138,6 +143,7 @@ Free Documentation License".
* largepost.c::
* sessions.c::
* callbackresponse.c::
+* asyncresponse.c::
* tlsauthentication.c::
* upgrade.c::
@end menu
@@ -190,6 +196,12 @@ Free Documentation License".
@verbatiminclude examples/callbackresponse.c
@end smalldisplay
+@node asyncresponse.c
+@section asyncresponse.c
+@smalldisplay
+@verbatiminclude examples/asyncresponse.c
+@end smalldisplay
+
@node tlsauthentication.c
@section tlsauthentication.c
@smalldisplay
diff --git a/doc/libmicrohttpd.texi b/doc/libmicrohttpd.texi
@@ -2482,10 +2482,27 @@ make sure to run @code{MHD_run} afterwards (before again calling
the set returned by @code{MHD_get_fdset} and you may end up with a
connection that is stuck until the next network activity.
+This function may be called from any thread, which is what makes it
+usable to hand work to threads of your own. In ``external'' select
+mode it writes to the inter-thread communication channel that
+@code{MHD_ALLOW_SUSPEND_RESUME} sets up, and since
+@code{MHD_get_fdset} adds the reading end of that channel to the read
+set, a call from another thread interrupts a @code{select} that your
+application is blocked in. That matters because
+@code{MHD_get_timeout} reports no timeout at all while every
+connection is suspended, so your application would otherwise block
+indefinitely. The channel is level triggered, so a resume issued
+between @code{MHD_get_fdset} and @code{select} cannot be lost.
+
You can check whether a connection is currently suspended using
@code{MHD_get_connection_info} by querying for
@code{MHD_CONNECTION_INFO_CONNECTION_SUSPENDED}.
+A worked example of suspending connections while worker threads
+produce the answers, driven by an external @code{select} loop, is
+given in the chapter ``Answering from another thread'' of the MHD
+tutorial.
+
@table @var
@item connection
the connection to resume