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 <vladimir@thecybershadow.net>
19  */
20 
21 module ae.sys.shutdown;
22 
23 /// Warning: the delegate may be called in an arbitrary thread.
24 void addShutdownHandler(void delegate() fn)
25 {
26 	handlers.add(fn);
27 }
28 
29 /// Calls all registered handlers.
30 void shutdown()
31 {
32 	foreach (fn; handlers.get())
33 		fn();
34 }
35 
36 private:
37 
38 import core.thread;
39 
40 void syncShutdown() nothrow @system
41 {
42 	try
43 	{
44 		thread_suspendAll();
45 		scope(exit) thread_resumeAll();
46 		shutdown();
47 	}
48 	catch (Throwable e)
49 	{
50 		import core.stdc.stdio;
51 		static if (__VERSION__ < 2068)
52 		{
53 			string s = e.msg;
54 			fprintf(stderr, "Unhandled error while shutting down:\r\n%.*s", s.length, s.ptr);
55 		}
56 		else
57 		{
58 			fprintf(stderr, "Unhandled error while shutting down:\r\n");
59 			_d_print_throwable(e);
60 		}
61 	}
62 }
63 
64 // http://d.puremagic.com/issues/show_bug.cgi?id=7016
65 version(Posix)
66 	import ae.sys.signals;
67 else
68 version(Windows)
69 	import core.sys.windows.windows;
70 
71 extern (C) void _d_print_throwable(Throwable t) nothrow;
72 
73 void register()
74 {
75 	version(Posix)
76 	{
77 		addSignalHandler(SIGTERM, { syncShutdown(); });
78 		addSignalHandler(SIGINT , { syncShutdown(); });
79 	}
80 	else
81 	version(Windows)
82 	{
83 		static shared bool closing = false;
84 
85 		static void win32write(string msg) nothrow
86 		{
87 			DWORD written;
88 			WriteConsoleA(GetStdHandle(STD_ERROR_HANDLE), msg.ptr, cast(uint)msg.length, &written, null);
89 		}
90 
91 		extern(Windows)
92 		static BOOL handlerRoutine(DWORD dwCtrlType) nothrow
93 		{
94 			if (!closing)
95 			{
96 				closing = true;
97 				win32write("Shutdown event received, shutting down.\r\n");
98 
99 				try
100 				{
101 					thread_attachThis();
102 					syncShutdown();
103 					thread_detachThis();
104 
105 					return TRUE;
106 				}
107 				catch (Throwable e)
108 				{
109 					win32write("Unhandled error while shutting down:\r\n");
110 					static if (__VERSION__ < 2068)
111 						win32write(e.msg);
112 					else
113 						_d_print_throwable(e);
114 				}
115 			}
116 			return FALSE;
117 		}
118 
119 		// https://issues.dlang.org/show_bug.cgi?id=12710
120 		SetConsoleCtrlHandler(cast(PHANDLER_ROUTINE)&handlerRoutine, TRUE);
121 	}
122 }
123 
124 synchronized class HandlerSet
125 {
126 	alias T = void delegate();
127 	private T[] handlers;
128 
129 	void add(T fn)
130 	{
131 		if (handlers.length == 0)
132 			register();
133 		handlers ~= cast(shared)fn;
134 	}
135 	const(T)[] get() { return cast(const(T[]))handlers; }
136 }
137 
138 shared HandlerSet handlers;
139 shared static this() { handlers = new HandlerSet; }