1 /**
2  * Integration with and wrapper around ae.sys.shutdown
3  * for networked (ae.net.asockets-based) applications.
4  *
5  * Unlike ae.sys.shutdown, the handlers are called from
6  * within the same thread they were registered from -
7  * provided that socketManager.loop() is running in that
8  * thread.
9  *
10  * License:
11  *   This Source Code Form is subject to the terms of
12  *   the Mozilla Public License, v. 2.0. If a copy of
13  *   the MPL was not distributed with this file, You
14  *   can obtain one at http://mozilla.org/MPL/2.0/.
15  *
16  * Authors:
17  *   Vladimir Panteleev <ae@cy.md>
18  */
19 
20 // TODO: Unify addShutdownHandler under a common API.
21 // The host program should decide which shutdown
22 // driver to use.
23 
24 // TODO: Add shuttingDown property
25 
26 module ae.net.shutdown;
27 
28 /// Register a handler to be called when a shutdown is requested.
29 /// The handler should close network connections and cancel timers,
30 /// thus removing all owned resources from the event loop which would
31 /// block it from exiting cleanly.
32 void addShutdownHandler(void delegate(scope const(char)[] reason) fn)
33 {
34 	handlers ~= fn;
35 	if (!registered)
36 		register();
37 }
38 
39 deprecated void addShutdownHandler(void delegate() fn)
40 {
41 	addShutdownHandler((scope const(char)[] /*reason*/) { fn(); });
42 } /// ditto
43 
44 /// Remove a previously-registered handler.
45 void removeShutdownHandler(void delegate(scope const(char)[] reason) fn)
46 {
47 	foreach (i, handler; handlers)
48 		if (fn is handler)
49 		{
50 			handlers = handlers[0 .. i] ~ handlers[i+1 .. $];
51 			return;
52 		}
53 	assert(false, "No such shutdown handler registered");
54 }
55 
56 /// Calls all registered handlers.
57 void shutdown(scope const(char)[] reason)
58 {
59 	foreach_reverse (fn; handlers)
60 		fn(reason);
61 }
62 
63 deprecated void shutdown()
64 {
65 	shutdown(null);
66 }
67 
68 private:
69 
70 static import ae.sys.shutdown;
71 import std.socket : socketPair;
72 import ae.net.asockets;
73 import ae.sys.data;
74 
75 // Per-thread
76 void delegate(scope const(char)[] reason)[] handlers;
77 bool registered;
78 
79 final class ShutdownConnection : TcpConnection
80 {
81 	Socket pinger;
82 
83 	this()
84 	{
85 		auto pair = socketPair();
86 		pair[0].blocking = false;
87 		super(pair[0]);
88 		pinger = pair[1];
89 		this.handleReadData = &onReadData;
90 		addShutdownHandler(&onShutdown); // for manual shutdown calls
91 		this.daemonRead = true;
92 	}
93 
94 	void ping(scope const(char)[] reason) //@nogc
95 	{
96 		static immutable ubyte[1] nullReason = [0];
97 		pinger.send(reason.length ? cast(ubyte[])reason : nullReason[]);
98 	}
99 
100 	void onShutdown(scope const(char)[] reason)
101 	{
102 		pinger.close();
103 	}
104 
105 	void onReadData(Data data)
106 	{
107 		data.asDataOf!char.enter((scope dataBytes) {
108 			auto reason = dataBytes.length == 1 && dataBytes[0] == 0 ? null : dataBytes;
109 			shutdown(reason);
110 		});
111 	}
112 }
113 
114 void register()
115 {
116 	registered = true;
117 	auto socket = new ShutdownConnection();
118 	ae.sys.shutdown.addShutdownHandler(&socket.ping);
119 }