Handling logout and system shutdown for recollindex on ms-windows.

This commit is contained in:
Jean-Francois Dockes 2015-10-29 16:04:59 +01:00
parent be2e9fff65
commit 8ce89f3ac1
6 changed files with 142 additions and 10 deletions

View File

@ -38,6 +38,23 @@
static pthread_t mainthread_id; 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 #ifndef _WIN32
static void siglogreopen(int) static void siglogreopen(int)
{ {
@ -81,7 +98,11 @@ void initAsyncSigs(void (*sigcleanup)(int))
} }
} }
} }
#else void recoll_exitready()
{
}
#else // _WIN32 ->
// Windows signals etc. // Windows signals etc.
// //
@ -92,15 +113,27 @@ void initAsyncSigs(void (*sigcleanup)(int))
// the process and calls the handler. The process exits when the // the process and calls the handler. The process exits when the
// handler returns or after at most 10S // handler returns or after at most 10S
// //
// In practise, only recollindex sets sigcleanup(), and the routine // This should also work, with different signals (CTRL_LOGOFF_EVENT,
// just sets a global termination flag. So we just call it and sleep, // CTRL_SHUTDOWN_EVENT) when the user exits or the system shuts down).
// hoping that cleanup does not take more than what Windows will let //
// us live. // 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 void (*l_sigcleanup)(int);
static HANDLE eWorkFinished = INVALID_HANDLE_VALUE;
static BOOL WINAPI CtrlHandler(DWORD fdwCtrlType) static BOOL WINAPI CtrlHandler(DWORD fdwCtrlType)
{ {
LOGDEB(("CtrlHandler\n"));
if (l_sigcleanup == 0) if (l_sigcleanup == 0)
return FALSE; return FALSE;
@ -110,17 +143,82 @@ static BOOL WINAPI CtrlHandler(DWORD fdwCtrlType)
case CTRL_BREAK_EVENT: case CTRL_BREAK_EVENT:
case CTRL_LOGOFF_EVENT: case CTRL_LOGOFF_EVENT:
case CTRL_SHUTDOWN_EVENT: case CTRL_SHUTDOWN_EVENT:
{
l_sigcleanup(SIGINT); 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; return TRUE;
}
default: default:
return FALSE; 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}; static const int catchedSigs[] = {SIGINT, SIGTERM};
void initAsyncSigs(void (*sigcleanup)(int)) void initAsyncSigs(void (*sigcleanup)(int))
{ {
DWORD tid;
// Install app signal handler // Install app signal handler
if (sigcleanup) { if (sigcleanup) {
l_sigcleanup = sigcleanup; l_sigcleanup = sigcleanup;
@ -130,7 +228,20 @@ void initAsyncSigs(void (*sigcleanup)(int))
} }
} }
} }
HANDLE hInvisiblethread =
CreateThread(NULL, 0, RunInvisibleWindowThread, NULL, 0, &tid);
SetConsoleCtrlHandler((PHANDLER_ROUTINE)CtrlHandler, TRUE); 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 #endif

View File

@ -57,4 +57,9 @@ extern void recoll_threadinit();
// Check if main thread // Check if main thread
extern bool recoll_ismainthread(); extern bool recoll_ismainthread();
// Should be called while exiting asap when critical cleanup (db
// close) has been performed. Only useful for the indexer (writes to
// the db), and only actually does something on Windows.
extern void recoll_exitready();
#endif /* _RCLINIT_H_INCLUDED_ */ #endif /* _RCLINIT_H_INCLUDED_ */

View File

@ -43,7 +43,9 @@ using std::vector;
#include "execmd.h" #include "execmd.h"
#include "recollindex.h" #include "recollindex.h"
#include "pathut.h" #include "pathut.h"
#ifndef _WIN32
#include "x11mon.h" #include "x11mon.h"
#endif
#include "subtreelist.h" #include "subtreelist.h"
typedef unsigned long mttcast; typedef unsigned long mttcast;
@ -510,9 +512,13 @@ bool startMonitor(RclConfig *conf, int opts)
// x11IsAlive() can't be called from ok() because both threads call it // x11IsAlive() can't be called from ok() because both threads call it
// and Xlib is not multithreaded. // and Xlib is not multithreaded.
#ifndef _WIN32
bool x11dead = !(opts & RCLMON_NOX11) && !x11IsAlive(); bool x11dead = !(opts & RCLMON_NOX11) && !x11IsAlive();
if (x11dead) if (x11dead)
LOGDEB(("RclMonprc: x11 is dead\n")); LOGDEB(("RclMonprc: x11 is dead\n"));
#else
bool x11dead = false;
#endif
if (!rclEQ.ok() || x11dead) { if (!rclEQ.ok() || x11dead) {
rclEQ.unlock(); rclEQ.unlock();
break; break;
@ -609,8 +615,13 @@ bool startMonitor(RclConfig *conf, int opts)
} }
LOGDEB(("Rclmonprc: calling queue setTerminate\n")); LOGDEB(("Rclmonprc: calling queue setTerminate\n"));
rclEQ.setTerminate(); rclEQ.setTerminate();
// Wait for receiver thread before returning
pthread_join(rcv_thrid, 0); // We used to wait for the receiver thread here before returning,
// but this is not useful and may waste time / risk problems
// during our limited time window for exiting. To be reviewed if
// we ever need several monitor invocations in the same process
// (can't foresee any reason why we'd want to do this).
// pthread_join(rcv_thrid, 0);
LOGDEB(("Monitor: returning\n")); LOGDEB(("Monitor: returning\n"));
return true; return true;
} }

View File

@ -90,6 +90,7 @@ static ConfIndexer *confindexer;
static void cleanup() static void cleanup()
{ {
deleteZ(confindexer); deleteZ(confindexer);
recoll_exitready();
} }
// Global stop request flag. This is checked in a number of place in the // Global stop request flag. This is checked in a number of place in the

View File

@ -2,6 +2,8 @@
QT -= core gui QT -= core gui
TARGET = recollindex TARGET = recollindex
CONFIG += console
CONFIG -= app_bundle
TEMPLATE = app TEMPLATE = app
DEFINES += BUILDING_RECOLL DEFINES += BUILDING_RECOLL
@ -9,7 +11,7 @@ DEFINES -= UNICODE
DEFINES -= _UNICODE DEFINES -= _UNICODE
DEFINES += _MBCS DEFINES += _MBCS
DEFINES += PSAPI_VERSION=1 DEFINES += PSAPI_VERSION=1
DEFINES += RCL_MONITOR
SOURCES += \ SOURCES += \
../../index/recollindex.cpp \ ../../index/recollindex.cpp \

View File

@ -2,6 +2,8 @@
QT -= core gui QT -= core gui
TARGET = recollq TARGET = recollq
CONFIG += console
CONFIG -= app_bundle
TEMPLATE = app TEMPLATE = app
DEFINES += BUILDING_RECOLL DEFINES += BUILDING_RECOLL