osmo_select_shutdown_request(): allow finishing pending writes on SIGTERM

Allow telling osmo_select_main* to only service pending writes (shutdown
mode). Introduce API fuctions to indicate a shutdown request, and find
out whether shutdown is complete.

Some osmo programs have a curious sleep of few seconds upon receiving
SIGTERM. The idea presumably was to finish off pending writes before
halting the program. But a sleep() on program exit is annoying,
especially when there usually are no pending writes, and when osmo-bsc
is launched numerous times for tests.

Change-Id: Ib94d4316924103459577087c2214188679db2227
diff --git a/src/select.c b/src/select.c
index 71ee7f6..f7eb5ea 100644
--- a/src/select.c
+++ b/src/select.c
@@ -69,6 +69,11 @@
 static __thread struct poll_state g_poll;
 #endif /* FORCE_IO_SELECT */
 
+/*! See osmo_select_shutdown_request() */
+static int _osmo_select_shutdown_requested = 0;
+/*! See osmo_select_shutdown_request() */
+static bool _osmo_select_shutdown_done = false;
+
 /*! Set up an osmo-fd. Will not register it.
  *  \param[inout] ofd Osmo FD to be set-up
  *  \param[in] fd OS-level file descriptor number
@@ -316,6 +321,7 @@
 	struct osmo_fd *ufd;
 	unsigned int i;
 	int work = 0;
+	int shutdown_pending_writes = 0;
 
 	for (i = 0; i < n_fd; i++) {
 		struct pollfd *p = &g_poll.poll[i];
@@ -340,6 +346,11 @@
 		/* make sure we never report more than the user requested */
 		flags &= ufd->when;
 
+		if (_osmo_select_shutdown_requested > 0) {
+			if (ufd->when & OSMO_FD_WRITE)
+				shutdown_pending_writes++;
+		}
+
 		if (flags) {
 			work = 1;
 			/* make sure to clear any log context before processing the next incoming message
@@ -351,6 +362,9 @@
 		}
 	}
 
+	if (_osmo_select_shutdown_requested > 0 && !shutdown_pending_writes)
+		_osmo_select_shutdown_done = true;
+
 	return work;
 }
 
@@ -370,7 +384,8 @@
 		return 0;
 
 	/* fire timers */
-	osmo_timers_update();
+	if (!_osmo_select_shutdown_requested)
+		osmo_timers_update();
 
 	OSMO_ASSERT(osmo_ctx->select);
 
@@ -596,6 +611,59 @@
 
 #endif /* HAVE_SYS_SIGNALFD_H */
 
+/*! Request osmo_select_* to only service pending OSMO_FD_WRITE requests. Once all writes are done,
+ * osmo_select_shutdown_done() returns true. This allows for example to send all outbound packets before terminating the
+ * process.
+ *
+ * Usage example:
+ *
+ * static void signal_handler(int signum)
+ * {
+ *         fprintf(stdout, "signal %u received\n", signum);
+ *
+ *         switch (signum) {
+ *         case SIGINT:
+ *         case SIGTERM:
+ *                 // If the user hits Ctrl-C the third time, just terminate immediately.
+ *                 if (osmo_select_shutdown_requested() >= 2)
+ *                         exit(-1);
+ *                 // Request write-only mode in osmo_select_main_ctx()
+ *                 osmo_select_shutdown_request();
+ *                 break;
+ *         [...]
+ * }
+ *
+ * main()
+ * {
+ *         signal(SIGINT, &signal_handler);
+ *         signal(SIGTERM, &signal_handler);
+ *
+ *         [...]
+ *
+ *         // After the signal_handler issued osmo_select_shutdown_request(), osmo_select_shutdown_done() returns true
+ *         // as soon as all write queues are empty.
+ *         while (!osmo_select_shutdown_done()) {
+ *                 osmo_select_main_ctx(0);
+ *         }
+ * }
+ */
+void osmo_select_shutdown_request()
+{
+	_osmo_select_shutdown_requested++;
+};
+
+/*! Return the number of times osmo_select_shutdown_request() was called before. */
+int osmo_select_shutdown_requested()
+{
+	return _osmo_select_shutdown_requested;
+};
+
+/*! Return true after osmo_select_shutdown_requested() was called, and after an osmo_select poll loop found no more
+ * pending OSMO_FD_WRITE on any registered socket. */
+bool osmo_select_shutdown_done() {
+	return _osmo_select_shutdown_done;
+};
+
 /*! @} */
 
 #endif /* _HAVE_SYS_SELECT_H */