Switch to side-by-side view

--- a/src/common/rclinit.cpp
+++ b/src/common/rclinit.cpp
@@ -38,6 +38,23 @@
 
 static pthread_t mainthread_id;
 
+// Signal etc. processing. We want to be able to properly close the
+// index if we are currently writing to it.
+//
+// This is active if the sigcleanup parameter to recollinit is set,
+// which only recollindex does. We arrange for the handler to be
+// called when process termination is requested either by the system
+// or a user keyboard intr.
+//
+// The recollindex handler just sets a global termination flag (plus
+// the cancelcheck thing), which are tested in all timeout loops
+// etc. When the flag is seen, the main thread processing returns, and
+// recollindex calls exit().
+//
+// The other parameter, to recollinit(), cleanup, is set as an
+// atexit() routine, it does the job of actually signalling the
+// workers to stop and tidy up. It's automagically called by exit().
+
 #ifndef _WIN32
 static void siglogreopen(int)
 {
@@ -81,7 +98,11 @@
 	}
     }
 }
-#else
+void recoll_exitready()
+{
+}
+
+#else // _WIN32 ->
 
 // Windows signals etc.
 //
@@ -92,15 +113,27 @@
 // the process and calls the handler. The process exits when the
 // handler returns or after at most 10S
 //
-// In practise, only recollindex sets sigcleanup(), and the routine
-// just sets a global termination flag. So we just call it and sleep,
-// hoping that cleanup does not take more than what Windows will let
-// us live.
+// This should also work, with different signals (CTRL_LOGOFF_EVENT,
+// CTRL_SHUTDOWN_EVENT) when the user exits or the system shuts down).
+//
+// Unfortunately, this is not the end of the story. It seems that in
+// recent Windows version "some kinds" of apps will not reliably
+// receive the signals. "Some kind" is variably defined, for example a
+// simple test program works when built with vs 2015, but not
+// mingw. See the following discussion thread for tentative
+// explanations, it seems that importing or not from user32.dll is the
+// determining factor.
+// https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/abf09824-4e4c-4f2c-ae1e-5981f06c9c6e/windows-7-console-application-has-no-way-of-trapping-logoffshutdown-event?forum=windowscompatibility
+// In any case, it appears that the only reliable way to be advised of
+// system shutdown or user exit is to create an "invisible window" and
+// process window messages, which we now do.
 
 static void (*l_sigcleanup)(int);
+static HANDLE eWorkFinished = INVALID_HANDLE_VALUE;
 
 static BOOL WINAPI CtrlHandler(DWORD fdwCtrlType)
 {
+    LOGDEB(("CtrlHandler\n"));
     if (l_sigcleanup == 0)
         return FALSE;
 
@@ -110,17 +143,82 @@
     case CTRL_BREAK_EVENT: 
     case CTRL_LOGOFF_EVENT: 
     case CTRL_SHUTDOWN_EVENT:
+    {
         l_sigcleanup(SIGINT);
-        Sleep(10000);
+        LOGDEB0(("CtrlHandler: waiting for exit ready\n"));
+	DWORD res = WaitForSingleObject(eWorkFinished, INFINITE);
+	if (res != WAIT_OBJECT_0) {
+            LOGERR(("CtrlHandler: exit ack wait failed\n"));
+	}
+        LOGDEB0(("CtrlHandler: got exit ready event, exiting\n"));
         return TRUE;
+    }
     default: 
         return FALSE; 
     } 
 } 
- 
+
+LRESULT CALLBACK MainWndProc(HWND hwnd , UINT msg , WPARAM wParam,
+                             LPARAM lParam)
+{
+    switch (msg) {
+    case WM_QUERYENDSESSION:
+    case WM_ENDSESSION:
+    case WM_DESTROY:
+    case WM_CLOSE:
+    {
+        l_sigcleanup(SIGINT);
+        LOGDEB(("MainWndProc: got end message, waiting for work finished\n"));
+	DWORD res = WaitForSingleObject(eWorkFinished, INFINITE);
+	if (res != WAIT_OBJECT_0) {
+            LOGERR(("MainWndProc: exit ack wait failed\n"));
+	}
+        LOGDEB(("MainWindowProc: got exit ready event, exiting\n"));
+        return TRUE;
+    }
+    default:
+        return DefWindowProc(hwnd, msg, wParam, lParam);
+    }
+    return TRUE;
+}
+
+bool CreateInvisibleWindow()
+{
+    HWND hwnd;
+    WNDCLASS wc = {0};
+
+    wc.lpfnWndProc = (WNDPROC)MainWndProc;
+    wc.hInstance = GetModuleHandle(NULL);
+    wc.hIcon = LoadIcon(GetModuleHandle(NULL), "TestWClass");
+    wc.lpszClassName = "TestWClass";
+    RegisterClass(&wc);
+
+    hwnd =
+        CreateWindowEx(0, "TestWClass", "TestWClass", WS_OVERLAPPEDWINDOW,
+                       CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
+                       CW_USEDEFAULT, (HWND) NULL, (HMENU) NULL,
+                       GetModuleHandle(NULL), (LPVOID) NULL);
+    if (!hwnd) {
+        return FALSE;
+    }
+    return TRUE;
+}
+
+DWORD WINAPI RunInvisibleWindowThread(LPVOID lpParam)
+{
+    MSG msg;
+    CreateInvisibleWindow();
+    while (GetMessage(&msg, (HWND) NULL , 0 , 0)) {
+        TranslateMessage(&msg);
+        DispatchMessage(&msg);
+    }
+    return 0;
+}
+
 static const int catchedSigs[] = {SIGINT, SIGTERM};
 void initAsyncSigs(void (*sigcleanup)(int))
 {
+    DWORD tid;
     // Install app signal handler
     if (sigcleanup) {
         l_sigcleanup = sigcleanup;
@@ -130,7 +228,20 @@
 	    }
         }
     }
+    HANDLE hInvisiblethread =
+        CreateThread(NULL, 0, RunInvisibleWindowThread, NULL, 0, &tid);
     SetConsoleCtrlHandler((PHANDLER_ROUTINE)CtrlHandler, TRUE);
+    eWorkFinished = CreateEvent(NULL, TRUE, FALSE, NULL);
+    if (eWorkFinished == INVALID_HANDLE_VALUE) {
+        LOGERR(("initAsyncSigs: error creating exitready event\n"));
+    }
+}
+void recoll_exitready()
+{
+    LOGDEB(("recoll_exitready()\n"));
+    if (!SetEvent(eWorkFinished)) {
+        LOGERR(("recoll_exitready: SetEvent failed\n"));
+    }
 }
 
 #endif