1 /*******************************************************************************
2 
3     Hosts a ITimeoutClient with a timeout value to be managed by the
4     TimeoutManager.
5 
6     Build flags:
7         -debug=TimeoutManager = verbose output
8 
9     Copyright:
10         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
11         All rights reserved.
12 
13     License:
14         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
15         Alternatively, this file may be distributed under the terms of the Tango
16         3-Clause BSD License (see LICENSE_BSD.txt for details).
17 
18 *******************************************************************************/
19 
20 module ocean.time.timeout.model.ExpiryRegistrationBase;
21 
22 
23 import ocean.meta.types.Qualifiers;
24 
25 import ocean.time.timeout.model.IExpiryRegistration,
26        ocean.time.timeout.model.ITimeoutClient;
27 
28 /*******************************************************************************
29 
30     The EBTree import and aliases should be in the TimeoutManager module and are
31     here only to work around DMD's flaw of supporting mutual module imports.
32 
33     TODO: Move to the TimeoutManager module when DMD is fixed.
34 
35 *******************************************************************************/
36 
37 import ocean.util.container.ebtree.EBTree64;
38 
39 alias EBTree64!() ExpiryTree;
40 
41 alias ExpiryTree.Node Expiry;
42 
43 /*******************************************************************************
44 
45     Struct storing a reference to an expiry time registry and an item in the
46     registry. An instance of this struct should be owned by each client which is
47     to be registered with the expiry time registry.
48 
49 *******************************************************************************/
50 
51 abstract class ExpiryRegistrationBase : IExpiryRegistration
52 {
53     /***************************************************************************
54 
55         Enables access of TimeoutManager internals.
56 
57     ***************************************************************************/
58 
59     interface ITimeoutManagerInternal
60     {
61         /***********************************************************************
62 
63             Registers registration and sets the timeout.
64 
65             Params:
66                 registration = IExpiryRegistration instance to register
67                 timeout_us   = timeout in microseconds from now
68 
69             Returns:
70                 expiry token: required for unregister(); "key" member reflects
71                 the expiration wall clock time.
72 
73         ***********************************************************************/
74 
75         Expiry* register ( IExpiryRegistration registration, ulong timeout_us );
76 
77         /***********************************************************************
78 
79             Unregisters IExpiryRegistration instance corresponding to expiry.
80 
81             Params:
82                 expiry = expiry token returned by register() when registering
83                          the IExpiryRegistration instance to unregister
84 
85             In:
86                 Must not be called from within timeout(). Doing so would still
87                 leave already fired timer events in the TimeoutManager internal
88                 list and their respective `timeout` will be called despite
89                 unregistration.
90 
91         ***********************************************************************/
92 
93         void unregister ( ref Expiry expiry );
94 
95         /***********************************************************************
96 
97             Unregisters the specified expiry. If the expiry is present in the
98             list of expired registrations being currently iterated over by
99             checkTimeouts, then it will be removed (its timeout method will not
100             be called). (This means that drop can be called from timeout
101             callbacks, unlike unregister.)
102 
103             Params:
104                 registration = expiry registration reference
105 
106         ***********************************************************************/
107 
108         void drop ( IExpiryRegistration registration );
109 
110         /***********************************************************************
111 
112             Returns:
113                 the current wall clock time as UNIX time in microseconds.
114 
115         ***********************************************************************/
116 
117         ulong now ( );
118     }
119 
120     /***************************************************************************
121 
122         Timeout client: Object that times out after register() has been called
123         when the time interval passed to register() has expired.
124 
125         The client instance is set by a subclass. The subclass must make sure
126         that a client instance is set before it calls register(). It may reset
127         the client instance to null after it has called unregister() (even if
128         unregister() throws an exception).
129 
130     ***************************************************************************/
131 
132     protected ITimeoutClient client = null;
133 
134     /***************************************************************************
135 
136         Reference to an expiry time item in the registry; this is the key
137         returned from register() and passed to unregister().
138         The expiry item is null if and only if the client is registered with the
139         timeout manager.
140 
141     ***************************************************************************/
142 
143     /* package(ocean) */
144     public Expiry* expiry = null;
145 
146     /***************************************************************************
147 
148         Object providing access to a timeout manager instance to
149         register/unregister a client with that timeout manager.
150 
151     ***************************************************************************/
152 
153     private ITimeoutManagerInternal mgr;
154 
155     /***************************************************************************
156 
157         "Timed out" flag: set by timeout() and cleared by register().
158 
159     ***************************************************************************/
160 
161     private bool timed_out_ = false;
162 
163     /***************************************************************************
164 
165         Makes sure we have a client while registered.
166 
167     ***************************************************************************/
168 
169     invariant ( )
170     {
171         assert (this.client !is null || this.expiry is null, "client required when registered");
172     }
173 
174     /***************************************************************************
175 
176         Constructor
177 
178         Params:
179             mgr = object providing access to a timeout manager instance to
180                   register/unregister a client with that timeout manager.
181 
182     ***************************************************************************/
183 
184     protected this ( ITimeoutManagerInternal mgr )
185     {
186         this.mgr = mgr;
187     }
188 
189     /***************************************************************************
190 
191         Unregisters the current client.
192         If a client is currently not registered, nothing is done.
193 
194         The subclass may reset the client instance to null after it has called
195         this method (even if it throws an exception).
196 
197         Returns:
198             true on success or false if no client was registered.
199 
200         In:
201             Must not be called from within timeout().
202 
203     ***************************************************************************/
204 
205     public bool unregister ( )
206     {
207         if (this.expiry)
208         {
209             try
210             {
211                 debug (TimeoutManager)
212                 {
213                     Stderr("*** unregister ")(this.id)('\n').flush();
214                 }
215 
216                 this.mgr.unregister(*this.expiry);
217 
218                 return true;
219             }
220             finally
221             {
222                 this.expiry = null;
223             }
224         }
225         else
226         {
227             return false;
228         }
229 
230     }
231 
232     /***************************************************************************
233 
234         Same as `unregister` but also removes expirty from list of currently
235         expired registrations. That means it can be called from `timeout`
236         callbacks, contrary to `unregister`.
237 
238         Returns:
239             true on success or false if no client was registered.
240 
241     ***************************************************************************/
242 
243     public bool drop ( )
244     {
245         this.mgr.drop(this);
246         return this.unregister();
247     }
248 
249     /***************************************************************************
250 
251         Returns:
252             the client timeout wall clock time as UNIX time in microseconds, if
253             a client is currently registered, or ulong.max otherwise.
254 
255     ***************************************************************************/
256 
257     public ulong expires ( )
258     {
259         return this.expiry? this.expiry.key : ulong.max;
260     }
261 
262     /***************************************************************************
263 
264         Returns:
265             the number of microseconds left until timeout from now, if a client
266             is currently registered, or long.max otherwise. A negative value
267             indicates that the client has timed out but was not yet
268             unregistered.
269 
270     ***************************************************************************/
271 
272     public long us_left ( )
273     in
274     {
275         assert (this.expiry, "not registered");
276     }
277     do
278     {
279         return this.expiry? this.expiry.key - this.mgr.now : long.max;
280     }
281 
282     /***************************************************************************
283 
284         Invokes the timeout() method of the client.
285 
286         Should only be called from inside the timeout manager.
287 
288         Returns:
289             current client which has been notified that it has timed out.
290 
291         In:
292             A client must be registered.
293 
294     ***************************************************************************/
295 
296     public ITimeoutClient timeout ( )
297     in
298     {
299         assert (this.expiry !is null, "timeout - no client");                   // The invariant makes sure that
300     }                                                                           // this.client !is null if this.expiry !is null.
301     do
302     {
303         debug ( TimeoutManager ) Stderr("*** timeout for ")(this.id)('\n').flush();
304 
305         this.timed_out_ = true;
306 
307         this.client.timeout();
308 
309         return this.client;
310     }
311 
312     /***************************************************************************
313 
314         Returns:
315             true if the client has timed out or false otherwise.
316 
317     ***************************************************************************/
318 
319     public bool timed_out ( )
320     {
321         return this.timed_out_;
322     }
323 
324     /***************************************************************************
325 
326         Returns:
327             true if the client is registered or false otherwise
328 
329     ***************************************************************************/
330 
331     public bool registered ( )
332     {
333         return this.expiry !is null;
334     }
335 
336     /***************************************************************************
337 
338         Sets the timeout for the client and registers it with the timeout
339         manager. On timeout the client will automatically be unregistered.
340         The client must not already be registered.
341 
342         The subclass must make sure that a client instance is set before it
343         calls this method. It may reset the client instance to null after it has
344         called unregister() (even if unregister() throws an exception).
345 
346         Params:
347             timeout_us = timeout in microseconds from now. 0 is ignored.
348 
349         Returns:
350             true if registered or false if timeout_us is 0.
351 
352         In:
353             - this.client must not be null.
354             - The client must not already be registered.
355 
356     ***************************************************************************/
357 
358     public bool register ( ulong timeout_us )
359     in
360     {
361         assert (this.expiry is null, "already registered");
362         assert (this.client !is null, "client required to register");
363     }
364     do
365     {
366         debug ( TimeoutManager ) Stderr("*** register ")(this.id)(": ");
367 
368         this.timed_out_ = false;
369 
370         if (timeout_us)
371         {
372             debug ( TimeoutManager ) Stderr(timeout_us)(" µs\n").flush();
373 
374             this.expiry = this.mgr.register(this, timeout_us);
375 
376             return true;
377         }
378         else
379         {
380             debug ( TimeoutManager ) Stderr("no timeout\n").flush();
381 
382             return false;
383         }
384     }
385 
386     /***************************************************************************
387 
388         Identifier string for debugging.
389 
390     ***************************************************************************/
391 
392     debug protected override cstring id ( )
393     {
394         return (this.client !is null)? this.client.id : typeof (this).stringof;
395     }
396 }