1 /**
2  * Application shutdown control (with SIGTERM handling).
3  * Different from atexit in that it controls initiation
4  * of graceful shutdown, as opposed to cleanup actions
5  * that are done as part of the shutdown process.
6  *
7  * Note: thread safety of this module is questionable.
8  * Use ae.net.shutdown for networked applications.
9  * TODO: transition to thread-safe centralized event loop.
10  *
11  * License:
12  *   This Source Code Form is subject to the terms of
13  *   the Mozilla Public License, v. 2.0. If a copy of
14  *   the MPL was not distributed with this file, You
15  *   can obtain one at http://mozilla.org/MPL/2.0/.
16  *
17  * Authors:
18  *   Vladimir Panteleev <ae@cy.md>
19  */
20 
21 module ae.sys.shutdown;
22 
23 /// Register a handler to be called when a shutdown is requested.
24 /// Warning: the delegate may be called in an arbitrary thread.
25 void addShutdownHandler(void delegate(scope const(char)[] reason) fn)
26 {
27 	handlers.add(fn);
28 }
29 
30 deprecated void addShutdownHandler(void delegate() fn)
31 {
32 	addShutdownHandler((scope const(char)[] reason) { fn(); });
33 }
34 
35 /// Calls all registered handlers.
36 void shutdown(scope const(char)[] reason)
37 {
38 	foreach (fn; handlers.get())
39 		fn(reason);
40 }
41 
42 deprecated void shutdown()
43 {
44 	shutdown(null);
45 }
46 
47 private:
48 
49 import core.thread;
50 
51 void syncShutdown(scope const(char)[] reason) nothrow @system
52 {
53 	try
54 	{
55 		thread_suspendAll();
56 		scope(exit) thread_resumeAll();
57 		shutdown(reason);
58 	}
59 	catch (Throwable e)
60 	{
61 		import core.stdc.stdio;
62 		static if (__VERSION__ < 2068)
63 		{
64 			string s = e.msg;
65 			fprintf(stderr, "Unhandled error while shutting down:\r\n%.*s", s.length, s.ptr);
66 		}
67 		else
68 		{
69 			fprintf(stderr, "Unhandled error while shutting down:\r\n");
70 			_d_print_throwable(e);
71 		}
72 	}
73 }
74 
75 // https://issues.dlang.org/show_bug.cgi?id=7016
76 version(Posix)
77 	import ae.sys.signals;
78 else
79 version(Windows)
80 	import core.sys.windows.windows;
81 
82 extern (C) void _d_print_throwable(Throwable t) nothrow;
83 
84 void register()
85 {
86 	version(Posix)
87 	{
88 		addSignalHandler(SIGTERM, { syncShutdown("SIGTERM"); });
89 		addSignalHandler(SIGINT , { syncShutdown("SIGINT" ); });
90 	}
91 	else
92 	version(Windows)
93 	{
94 		static shared bool closing = false;
95 
96 		static void win32write(string msg) nothrow
97 		{
98 			DWORD written;
99 			WriteConsoleA(GetStdHandle(STD_ERROR_HANDLE), msg.ptr, cast(uint)msg.length, &written, null);
100 		}
101 
102 		extern(Windows)
103 		static BOOL handlerRoutine(DWORD dwCtrlType) nothrow
104 		{
105 			if (!closing)
106 			{
107 				closing = true;
108 				win32write("Shutdown event received, shutting down.\r\n");
109 
110 				string reason;
111 				switch (dwCtrlType)
112 				{
113 					case CTRL_C_EVENT       : reason = "CTRL_C_EVENT"       ; break;
114 					case CTRL_BREAK_EVENT   : reason = "CTRL_BREAK_EVENT"   ; break;
115 					case CTRL_CLOSE_EVENT   : reason = "CTRL_CLOSE_EVENT"   ; break;
116 					case CTRL_LOGOFF_EVENT  : reason = "CTRL_LOGOFF_EVENT"  ; break;
117 					case CTRL_SHUTDOWN_EVENT: reason = "CTRL_SHUTDOWN_EVENT"; break;
118 					default: reason = "Unknown dwCtrlType"; break;
119 				}
120 
121 				try
122 				{
123 					thread_attachThis();
124 					syncShutdown(reason);
125 					thread_detachThis();
126 
127 					return TRUE;
128 				}
129 				catch (Throwable e)
130 				{
131 					win32write("Unhandled error while shutting down:\r\n");
132 					static if (__VERSION__ < 2068)
133 						win32write(e.msg);
134 					else
135 						_d_print_throwable(e);
136 				}
137 			}
138 			return FALSE;
139 		}
140 
141 		// https://issues.dlang.org/show_bug.cgi?id=12710
142 		SetConsoleCtrlHandler(cast(PHANDLER_ROUTINE)&handlerRoutine, TRUE);
143 	}
144 }
145 
146 synchronized class HandlerSet
147 {
148 	alias T = void delegate(scope const(char)[] reason);
149 	private T[] handlers;
150 
151 	void add(T fn)
152 	{
153 		if (handlers.length == 0)
154 			register();
155 		handlers ~= cast(shared)fn;
156 	}
157 	const(T)[] get() { return cast(const(T[]))handlers; }
158 }
159 
160 shared HandlerSet handlers;
161 shared static this() { handlers = new HandlerSet; }