1 /******************************************************************************
2 
3     Base class for registrable client objects for the SelectDispatcher
4 
5     Contains the three things that the SelectDispatcher needs:
6         1. the I/O device instance
7         2. the I/O events to register the device for
8         3. the event handler to invocate when an event occured for the device
9 
10     In addition a subclass may override finalize(). When handle() returns false
11     or throws an Exception, the ISelectClient instance is unregistered from the
12     SelectDispatcher and finalize() is invoked.
13 
14     Copyright:
15         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
16         All rights reserved.
17 
18     License:
19         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
20         Alternatively, this file may be distributed under the terms of the Tango
21         3-Clause BSD License (see LICENSE_BSD.txt for details).
22 
23  ******************************************************************************/
24 
25 module ocean.io.select.client.model.ISelectClient;
26 
27 
28 
29 
30 import ocean.meta.types.Qualifiers;
31 
32 import ocean.core.Verify;
33 
34 import ocean.io.select.client.model.ISelectClientInfo;
35 
36 import ocean.sys.Epoll;
37 
38 import ocean.io.model.IConduit: ISelectable;
39 
40 import ocean.time.timeout.model.ITimeoutClient,
41        ocean.time.timeout.model.IExpiryRegistration: IExpiryRegistration;
42 
43 import ocean.core.Array: concat, append;
44 
45 import ocean.text.util.ClassName;
46 
47 debug import ocean.io.Stdout;
48 
49 import ocean.text.convert.Formatter;
50 
51 /******************************************************************************
52 
53     ISelectClient abstract class
54 
55  ******************************************************************************/
56 
57 public abstract class ISelectClient : ITimeoutClient, ISelectable, ISelectClientInfo
58 {
59     /**************************************************************************
60 
61         Convenience alias to avoid public imports
62 
63      **************************************************************************/
64 
65     public alias .ISelectable ISelectable;
66 
67     /**************************************************************************
68 
69         Enum of event types
70 
71      **************************************************************************/
72 
73     alias Epoll.Event Event;
74 
75     /**************************************************************************
76 
77         Enum of the status when finalize() is called.
78 
79      **************************************************************************/
80 
81     enum FinalizeStatus : uint
82     {
83         Success = 0,
84         Error,
85         Timeout
86     }
87 
88     /**************************************************************************
89 
90         I/O device instance
91 
92         Note: Conforming to the name convention used in ocean.io.selector, the
93         ISelectable instance is named "conduit" although ISelectable and
94         IConduit are distinct from each other. However, in most application
95         cases the provided instance will originally implement both ISelectable
96         and IConduit (as, for example, ocean.io.device.Device and
97         ocean.net.device.Socket).
98 
99      **************************************************************************/
100 
101     public abstract Handle fileHandle ( );
102 
103     /**************************************************************************
104 
105         Events to register the conduit for.
106 
107      **************************************************************************/
108 
109     public abstract Event events ( );
110 
111     /**************************************************************************
112 
113         Connection time out in microseconds. Effective only when used with an
114         EpollSelectDispatcher which has timeouts enabled. A value of 0 has no
115         effect.
116 
117      **************************************************************************/
118 
119     public ulong timeout_us = 0;
120 
121     /**************************************************************************
122 
123         Timeout expiry registration instance
124 
125      **************************************************************************/
126 
127     private IExpiryRegistration expiry_registration_;
128 
129     /**************************************************************************
130 
131         The "my conduit is registered with epoll with my events and me as
132         attachment" flag, set by registered() and cleared by unregistered().
133 
134         Notes:
135             1. The system can automatically unregister the conduit when its
136                file descriptor is closed; when this happens this flag is true by
137                mistake. The EpollSelectDispatcher is aware of that. However,
138                this flag can never be false by mistake.
139             2. There are use cases where several instances of this class share
140                the same conduit. Exactly one instance is associated to the
141                conduit registration and has is_registered_ = true. For the other
142                instances is_registered_ is false although their conduit is in
143                fact registered with epoll.
144 
145      **************************************************************************/
146 
147     private bool is_registered_;
148 
149     /**************************************************************************
150 
151         Sets the timeout manager expiry registration.
152 
153         Params:
154             expiry_registration_ = timeout manager expiry registration
155 
156         Returns:
157             timeout manager expiry registration
158 
159      **************************************************************************/
160 
161     public IExpiryRegistration expiry_registration ( IExpiryRegistration expiry_registration_ )
162     {
163         return this.expiry_registration_ = expiry_registration_;
164     }
165 
166     /***************************************************************************
167 
168         Returns:
169             true if this client has timed out or false otherwise.
170 
171     ***************************************************************************/
172 
173     public bool timed_out ( )
174     {
175         return (this.expiry_registration_ !is null)?
176                     this.expiry_registration_.timed_out : false;
177     }
178 
179     /**************************************************************************
180 
181         I/O event handler
182 
183         Params:
184              event   = identifier of I/O event that just occured on the device
185 
186         Returns:
187             true if the handler should be called again on next event occurrence
188             or false if this instance should be unregistered from the
189             SelectDispatcher.
190 
191      **************************************************************************/
192 
193     abstract public bool handle ( Event event );
194 
195     /**************************************************************************
196 
197         Timeout method, called after a timeout occurs in the SelectDispatcher
198         eventLoop. Intended to be overridden by a subclass if required.
199 
200      **************************************************************************/
201 
202     public void timeout ( ) { }
203 
204     /**************************************************************************
205 
206         Finalize method, called after this instance has been unregistered from
207         the Dispatcher. Intended to be overridden by a subclass if required.
208 
209         Params:
210             status = status why this method is called
211 
212      **************************************************************************/
213 
214     public void finalize ( FinalizeStatus status ) { }
215 
216     /**************************************************************************
217 
218         Error reporting method, called when an Exception is caught from
219         handle(). Calls the error_() method, which should be overridden by a
220         subclass if required.
221 
222         Note that this method will catch all exceptions thrown by the error_()
223         method. This is to prevent unhandled exceptions flying out of the select
224         dispatcher and bringing down the event loop.
225 
226         Params:
227             exception = Exception thrown by handle()
228             event     = Selector event while exception was caught
229 
230      **************************************************************************/
231 
232     final public void error ( Exception exception, Event event = Event.None )
233     {
234         try
235         {
236             this.error_(exception, event);
237         }
238         catch ( Exception e )
239         {
240             // Note: this should *never* happen! In case it ever does, here's
241             // a helpful printout to notify the application programmer.
242             debug Stderr.formatln(
243                 "Very bad: Exception thrown from inside ISelectClient.error() delegate! -- {} ({}:{})",
244                 e.message(), e.file, e.line
245             );
246         }
247     }
248 
249     protected void error_ ( Exception exception, Event event ) { }
250 
251 
252     /**************************************************************************
253 
254         Obtains the current error code of the underlying I/O device.
255 
256         To be overridden by a subclass for I/O devices that support querying a
257         device specific error status (e.g. sockets with getsockopt()).
258 
259         Returns:
260             the current error code of the underlying I/O device.
261 
262      **************************************************************************/
263 
264     public int error_code ( )
265     {
266         return 0;
267     }
268 
269     /**************************************************************************
270 
271         Register method, called after this client is registered with the
272         SelectDispatcher. Intended to be overridden by a subclass if required.
273 
274      **************************************************************************/
275 
276     final public void registered ( )
277     {
278         verify(!this.is_registered_, classname(this) ~ ".registered(): already registered");
279 
280         this.is_registered_ = true;
281 
282         try if (this.expiry_registration_ !is null)
283         {
284             this.expiry_registration_.register(this.timeout_us);
285         }
286         finally
287         {
288             this.registered_();
289         }
290     }
291 
292     /**************************************************************************
293 
294         Unregister method, called after this client is unregistered from the
295         SelectDispatcher. Intended to be overridden by a subclass if required.
296 
297      **************************************************************************/
298 
299     final public void unregistered ( )
300     {
301         verify(this.is_registered_, classname(this) ~ ".unregistered(): not registered");
302 
303         this.is_registered_ = false;
304 
305         try if (this.expiry_registration_ !is null)
306         {
307             this.expiry_registration_.unregister();
308         }
309         finally
310         {
311             this.unregistered_();
312         }
313     }
314 
315     /**************************************************************************
316 
317         Returns true if this.conduit is currently registered for this.events
318         with this as attachment. Returns false if this.conduit is not registered
319         with epoll or, when multiple instances of this class share the same
320         conduit, if it is registered with another instance.
321 
322         Note that the returned value can be true by mistake when epoll
323         unexpectedly unregistered the conduit file descriptor as it happens when
324         the file descriptor is closed (e.g. on error). However, the returned
325         value cannot be true by mistake.
326 
327         Returns:
328             true if this.conduit is currently registered for this.events with
329             this as attachment or false otherwise.
330 
331      **************************************************************************/
332 
333     public bool is_registered ( )
334     {
335         return this.is_registered_;
336     }
337 
338     /***************************************************************************
339 
340         ISelectClientInfo method.
341 
342         Returns:
343             I/O timeout value of client in microseconds. A value of 0 means that
344             no timeout is set for this client
345 
346     ***************************************************************************/
347 
348     public ulong timeout_value_us ( )
349     {
350         return this.timeout_us;
351     }
352 
353     /**************************************************************************
354 
355         Called by registered(); may be overridden by a subclass.
356 
357      **************************************************************************/
358 
359     protected void registered_ ( ) { }
360 
361     /**************************************************************************
362 
363         Called by unregistered(); may be overridden by a subclass.
364 
365      **************************************************************************/
366 
367     protected void unregistered_ ( ) { }
368 
369     /**************************************************************************
370 
371         Returns an identifier string of this instance. Defaults to the name of
372         the class, but may be overridden if more detailed information is
373         required.
374 
375         Note that this method is only ever called in cases where one or more
376         debug compile flags are switched on (ISelectClient, for example). Hence
377         the loop to extract the class name from the full module/class name
378         string is not considered a performance problem.
379 
380         Returns:
381              identifier string of this instance
382 
383      **************************************************************************/
384 
385     public cstring id ( )
386     {
387         return classname(this);
388     }
389 
390     /***************************************************************************
391 
392         Returns a string describing this client, for use in debug messages.
393 
394         Returns:
395             string describing client
396 
397     ***************************************************************************/
398 
399     debug public override istring toString ( )
400     {
401         import ocean.core.TypeConvert: assumeUnique;
402         mstring to_string_buf;
403         this.fmtInfo((cstring chunk) {to_string_buf ~= chunk;});
404         return assumeUnique(to_string_buf);
405     }
406 
407     /***************************************************************************
408 
409         Produces a string containing information about this instance: Dynamic
410         type, file descriptor and events.
411 
412         Params:
413             sink = `Layout.convert()`-style sink of string chunks
414 
415     ***************************************************************************/
416 
417     public void fmtInfo ( scope void delegate ( cstring chunk ) sink )
418     {
419         sformat(
420             (cstring chunk) {sink(chunk); },
421             "{} fd={} events=", this.id, this.fileHandle
422         );
423         foreach ( event, name; epoll_event_t.event_to_name )
424         {
425             if ( this.events & event )
426             {
427                 sink(name);
428             }
429         }
430     }
431 }
432 
433 /******************************************************************************
434 
435     IAdvancedSelectClient abstract class
436 
437     Provides a set of interfaces which can be implemented by classes which
438     desire notification of various events in the select client, and a set of
439     corresponding methods which allow the user to pass an instance of these
440     interfaces to an instance of this class:
441         * IFinalizer interface, set by the finalizer() method, called when a
442           select client is unregistered.
443         * IErrorReporter interface, set by the error_reporter() method, called
444           when an error occurs while handling a select client.
445         * ITimeoutReporter interface, set by the timeout_reporter() method,
446           called when a timeout occurs while handling a select client.
447         * IConnectionInfo interface, set by the connection_info() method, called
448           in debug(ISelectClient) mode when the selector wishes to get a string
449           containing information about the connection a select client is using.
450 
451  ******************************************************************************/
452 
453 abstract class IAdvancedSelectClient : ISelectClient
454 {
455     /**************************************************************************/
456 
457     interface IFinalizer
458     {
459         alias IAdvancedSelectClient.FinalizeStatus FinalizeStatus;
460 
461         void finalize ( FinalizeStatus status );
462     }
463 
464     /**************************************************************************/
465 
466     interface IErrorReporter
467     {
468         void error ( Exception exception, Event event = Event.None );
469     }
470 
471     /**************************************************************************/
472 
473     interface ITimeoutReporter
474     {
475         void timeout ( );
476     }
477 
478     /**************************************************************************
479 
480         Interface instances
481 
482      **************************************************************************/
483 
484     private IFinalizer       finalizer_        = null;
485     private IErrorReporter   error_reporter_   = null;
486     private ITimeoutReporter timeout_reporter_ = null;
487 
488     /**************************************************************************
489 
490         Sets the Finalizer. May be set to null to disable finalizing.
491 
492         Params:
493             finalizer_ = IFinalizer instance
494 
495      **************************************************************************/
496 
497     public void finalizer ( IFinalizer finalizer_ )
498     {
499         this.finalizer_ = finalizer_;
500     }
501 
502     /**************************************************************************
503 
504         Sets the TimeoutReporter. May be set to null to disable timeout
505         reporting.
506 
507         Params:
508             timeout_reporter_ = ITimeoutReporter instance
509 
510      **************************************************************************/
511 
512     public void timeout_reporter ( ITimeoutReporter timeout_reporter_ )
513     {
514         this.timeout_reporter_ = timeout_reporter_;
515     }
516 
517     /**************************************************************************
518 
519         Sets the Error Reporter. May be set to null to disable error reporting.
520 
521         Params:
522             error_reporter_ = IErrorReporter instance
523 
524      **************************************************************************/
525 
526     public void error_reporter ( IErrorReporter error_reporter_ )
527     {
528         this.error_reporter_ = error_reporter_;
529     }
530 
531     /**************************************************************************
532 
533         Finalize method, called after this instance has been unregistered from
534         the Dispatcher.
535 
536      **************************************************************************/
537 
538     public override void finalize ( FinalizeStatus status )
539     {
540         if (this.finalizer_ !is null)
541         {
542             this.finalizer_.finalize(status);
543         }
544     }
545 
546     /**************************************************************************
547 
548         Error reporting method, called when an Exception is caught from
549         super.handle().
550 
551         Params:
552             exception = Exception thrown by handle()
553             event     = Selector event while exception was caught
554 
555      **************************************************************************/
556 
557     override protected void error_ ( Exception exception, Event event )
558     {
559         if (this.error_reporter_)
560         {
561             this.error_reporter_.error(exception, event);
562         }
563     }
564 
565     /**************************************************************************
566 
567         Timeout method, called after this a timeout has occurred in the
568         SelectDispatcher.
569 
570      **************************************************************************/
571 
572     override public void timeout ( )
573     {
574         if (this.timeout_reporter_)
575         {
576             this.timeout_reporter_.timeout();
577         }
578     }
579 }