1 #ifndef NETCACHE__SRV_TASKS__HPP
2 #define NETCACHE__SRV_TASKS__HPP
3 /*  $Id: srv_tasks.hpp 570772 2018-09-14 12:35:21Z gouriano $
4  * ===========================================================================
5  *
6  *                            PUBLIC DOMAIN NOTICE
7  *               National Center for Biotechnology Information
8  *
9  *  This software/database is a "United States Government Work" under the
10  *  terms of the United States Copyright Act.  It was written as part of
11  *  the author's official duties as a United States Government employee and
12  *  thus cannot be copyrighted.  This software/database is freely available
13  *  to the public for use. The National Library of Medicine and the U.S.
14  *  Government have not placed any restriction on its use or reproduction.
15  *
16  *  Although all reasonable efforts have been taken to ensure the accuracy
17  *  and reliability of the software and data, the NLM and the U.S.
18  *  Government do not and cannot warrant the performance or results that
19  *  may be obtained by using this software or data. The NLM and the U.S.
20  *  Government disclaim all warranties, express or implied, including
21  *  warranties of performance, merchantability or fitness for any particular
22  *  purpose.
23  *
24  *  Please cite the author in any work or product based on this material.
25  *
26  * ===========================================================================
27  *
28  * Authors:  Pavel Ivanov
29  *
30  * File Description:
31  */
32 
33 
34 namespace intr = boost::intrusive;
35 
36 
37 BEGIN_NCBI_SCOPE
38 
39 // adds task statistics
40 #define __NC_TASKS_MONITOR  0
41 // uses Boost intrusive set to hold task stat (versus std::set)
42 // uses member_hook, because base_hook caused conflicts on linking
43 #define __NC_TASKS_INTR_SET 1
44 extern Uint8 s_CurJiffies;
45 
46 
47 class CRequestContext;
48 struct STimerTicket;
49 class CSrvSocketTask;
50 
51 struct SSrvTaskList_tag;
52 typedef intr::slist_member_hook<intr::tag<SSrvTaskList_tag> >   TSrvTaskListHook;
53 
54 
55 /// This class is for TaskServer's internal use only.
56 /// This class executes deletion of CSrvTask object. By design it is possible
57 /// that when CSrvTask::Terminate() is called (or more exactly when
58 /// ExecuteSlice() which probably executes at this time has finished) other
59 /// threads still have pointer to this task (including probably inside
60 /// TaskServer's code). But it's assumed (and upper code have to maintain this
61 /// assumption) that once Terminate() is called any other thread will forget
62 /// about this pointer soon and won't recall it from anywhere ever. With this
63 /// assumption in mind CSrvTaskTerminator just need to wait for RCU grace
64 /// period to pass and then it will be safe to delete CSrvTask object.
65 class CSrvTaskTerminator : public CSrvRCUUser
66 {
67 public:
68     CSrvTaskTerminator(void);
69     virtual ~CSrvTaskTerminator(void);
70 
71 private:
72     virtual void ExecuteRCU(void);
73 };
74 
75 
76 /// Main working entity in TaskServer. It's called "task" because it should
77 /// work like task/process in OS with cooperative multi-tasking. Everything
78 /// that task want to do should be divided into relatively small pieces --
79 /// slices. OS (TaskServer in this case) will provide time slices to each task,
80 /// they should execute a portion of their work during this slice and then
81 /// return control back to OS to give other tasks a chance to have their time
82 /// slice of the processor (thread in case of TaskServer). Each slice of work
83 /// done by the task should be less than jiffy (0.01 second by default),
84 /// preferably one or two orders of magnitude less than jiffy. This way
85 /// TaskServer will be able to schedule all tasks more effectively and spread
86 /// the load evenly between threads.
87 class CSrvTask
88 {
89 public:
90     CSrvTask(void);
91     /// Set this task "runnable", i.e. available for execution, having some
92     /// pending work requiring some processing power. Call to this method
93     /// guarantees that ExecuteSlice() will be called some time later.
94     /// If another execution of ExecuteSlice() is in progress at the time
95     /// of call to SetRunnable() then TaskServer will call ExecuteSlice()
96     /// again some time after current ExecuteSlice() finishes. This behavior
97     /// guarantees that if ExecuteSlice() somewhere checks for more available
98     /// work, doesn't find it, decides to return and some other thread calls
99     /// SetRunnable() at this moment task will have a chance to check for
100     /// available work in next call to ExecuteSlice().
101     /// This method is thread-safe and can be called from any thread and any
102     /// context.
103     void SetRunnable(bool boost = false);
104     /// This call is basically equivalent to SetRunnable() but with guarantee
105     /// that task will be scheduled for execution no earlier than delay_sec
106     /// later (approximately, can be up to a second less if measured in
107     /// absolute wall time). If SetRunnable() is called while task is waiting
108     /// for its delay timer then timer is canceled and task should set this
109     /// timer inside ExecuteSlice() again if it still needs it. Timers are
110     /// never set if TaskServer is shutting down (CTaskServer::IsInShutdown()
111     /// returns TRUE). In this case ExecuteSlice() will be called immediately
112     /// after call to RunAfter() and task should recognize state of shutting
113     /// down and behave accordingly.
114     /// This method is not thread-safe and should be called only from inside
115     /// ExecuteSlice() method.
116     void RunAfter(Uint4 delay_sec);
117     /// Stops task's execution and deletes it. This method should be used for
118     /// any object derived from CSrvTask instead of direct destruction,
119     /// otherwise in some situations TaskServer could dereference pointer to
120     /// this task after its deletion. The only exception from this rule could
121     /// be when task has been just created and SetRunnable() was never called
122     /// for it. See more comments on this in the description of
123     /// CSrvTaskTerminator.
124     /// This method is thread-safe although it's preferred to call it from
125     /// inside ExecuteSlice() method.
126     /// This method is virtual for internal use only - CSrvSocketTask should
127     /// use different termination sequence. Do not ever override this method
128     /// in any other class.
129     virtual void Terminate(void);
130 
131     /// Set and retrieve task's priority. TaskServer has simple notion of
132     /// priority: all tasks by default have priority 1. For each slice of task
133     /// with priority 2 TaskServer executes 2 slices of priority 1. For each
134     /// slice of task with priority 3 TaskServer executes 3 tasks with
135     /// priority 1 and 2 tasks with priority 2 (rounding goes up, error doesn't
136     /// accumulate, i.e. for 2 tasks with priority 3 it executes 3 tasks with
137     /// priority 2). And so on. But this rule works only if there are tasks
138     /// with higher priorities in the queue. If only tasks with low priority
139     /// are executing at the moment they will take all available time slices.
140     /// Counting of already executed tasks and their priorities is done on a
141     /// per-jiffy basis, i.e. if there were only high priority tasks for some
142     /// time and then task with low priority appeared it probably will be
143     /// executed within 0.01 second.
144     /// That said priority system is not quite usable now because it needs
145     /// things like priority inheritance from one task to the one created
146     /// by it, priority combination in CSrvTransitionTask and probably some
147     /// other mechanisms to work exactly as expected.
148     void SetPriority(Uint4 prty);
149     Uint4 GetPriority(void);
150 
151     /// Create new diagnostic context for this task to work in. See comments
152     /// to SetDiagCtx() on the effect of that.
153     void CreateNewDiagCtx(void);
154     /// Set diagnostic context for this task to work in. From this moment on
155     /// all log messages made from inside ExecuteSlice() of this task will be
156     /// assigned to the provided request context. Diagnostic contexts can be
157     /// nested -- second call to SetDiagCtx() sets new context of this task
158     /// and next call to ReleaseDiagCtx() restores first context.
159     void SetDiagCtx(CRequestContext* ctx);
160     /// Get current diagnostic context for the task.
161     CRequestContext* GetDiagCtx(void);
162     /// Releases current diagnostic context of the task. Method must be called
163     /// for all contexts created via CreateNewDiagCtx() or set via
164     /// SetDiagCtx(). You cannot make more calls and you must not make less
165     /// calls than CreateNewDiagCtx() and SetDiagCtx() combined. Both
166     /// erroneous situations will result in program abort.
167     void ReleaseDiagCtx(void);
168 
169     static void PrintState(CSrvSocketTask& task);
170 
171 // Consider this section protected as it's public for internal use only
172 // to minimize implementation-specific clutter in headers.
173 public:
174     /// This is the main method to do all work this task should do. This
175     /// method will be called periodically while somebody calls SetRunnable()
176     /// or RunAfter() on this task. But number of calls to this method won't
177     /// match number of calls to SetRunnable() -- if several calls to
178     /// SetRunnable() are made before ExecuteSlice() had a chance to execute
179     /// it will be called only once. Task's code inside ExecuteSlice() should
180     /// understand itself how much work it needs to do and call SetRunnable()
181     /// again if needed (or do all the work before returning from
182     /// ExecuteSlice()).
183     /// Parameter thr_num is number of thread on which ExecuteSlice() is
184     /// called.
185     virtual void ExecuteSlice(TSrvThreadNum thr_num) = 0;
186 
187 private:
188     CSrvTask(const CSrvTask&);
189     CSrvTask& operator= (const CSrvTask&);
190 
191 // Consider this section private as it's public for internal use only
192 // to minimize implementation-specific clutter in headers.
193 public:
194     virtual ~CSrvTask(void);
195 
196     /// This is the real time slice execution method called from TaskServer.
197     /// By default it just calls ExecuteSlice(). But CSrvSocketTask needs to
198     /// do some work before calling into user's code (or even instead of it),
199     /// thus this method was added and made virtual.
200     virtual void InternalRunSlice(TSrvThreadNum thr_num);
201 
202 
203     /// Hook to put this task into TSrvTaskList.
204     TSrvTaskListHook m_TaskListHook;
205     /// Thread number where this task was executed last time.
206     TSrvThreadNum m_LastThread;
207     /// Bit-OR of flags for this task. For possible flag values see
208     /// scheduler.hpp.
209     TSrvTaskFlags m_TaskFlags;
210     /// Time (in seconds) when the task was active last time, i.e. when
211     /// InternalRunSlice() was called last time.
212     int m_LastActive;
213     /// Task's priority.
214     Uint4 m_Priority;
215     /// Current diagnostic context for this task.
216     CRequestContext* m_DiagCtx;
217     /// Nested diagnostic contexts of this task. This variable is not NULL
218     /// if this task have ever used nested contexts and contains array of
219     /// context pointers with deepest context (the one assigned to task first)
220     /// in the element 0, context set later in the element 1 etc.
221     CRequestContext** m_DiagChain;
222 //#if __NC_MEMMAN_USE_STD_MALLOC
223     size_t m_DiagChainSize;
224     /// Timer ticket assigned to this task when it calls RunAfter().
225     STimerTicket* m_Timer;
226     /// Object that will delete this task after call to Terminate().
227     CSrvTaskTerminator m_Terminator;
228 #if __NC_TASKS_MONITOR
229     string m_TaskName;
230 #if __NC_TASKS_INTR_SET
231     intr::set_member_hook<> m_intr_member_hook;
232 #endif
233 #endif
234 };
235 
236 
237 // Helper types to be used only inside TaskServer. It's placed here only to
238 // keep all relevant pieces in one place.
239 typedef intr::member_hook<CSrvTask,
240                           TSrvTaskListHook,
241                           &CSrvTask::m_TaskListHook>    TSrvTaskListOpt;
242 typedef intr::slist<CSrvTask,
243                     TSrvTaskListOpt,
244                     intr::cache_last<true>,
245                     intr::constant_time_size<true>,
246                     intr::size_type<Uint4> >            TSrvTaskList;
247 
248 
249 /// Special task which executes as finite state machine.
250 /// To implement a task that will be executed using FSM you need to declare
251 /// its class like this:
252 ///
253 /// class CMyClass : public CSrvStatesTask<CMyClass>
254 ///
255 /// All machine states are referenced via pointer to function implementing
256 /// this state. To make state referencing more convenient there's a typedef
257 /// Me pointing to your class (CMyClass in above example). So you can
258 /// reference to some state as &Me::x_StateFunctionName (all state
259 /// implementing methods should be private as they shouldn't be used anywhere
260 /// outside this state machine). Methods implementing states shouldn't take
261 /// any arguments and should return next state for this machine to go to
262 /// (for convenience there's typedef State to use as return type of methods).
263 /// When state-implementing method returns some non-NULL pointer to next state
264 /// machine will go to next state immediately. If state-implementing method
265 /// returns NULL it means that state of the machine shouldn't change and
266 /// the same state-implementing method should be called next time TaskServer
267 /// calls ExecuteSlice() of this task. If you need to change machine's state
268 /// but not execute it immediately or yield thread's time to other tasks
269 /// waiting for execution you can use a sequence like this:
270 ///
271 /// SetState(&Me::x_NextState);
272 /// SetRunnable();  // use RunAfter() here if you need to execute later
273 /// return NULL;
274 ///
275 /// Initial state of the machine should be set using SetState() in your class
276 /// constructor.
277 ///
278 /// Virtual inheritance from CSrvTask is used to allow derived classes to
279 /// combine several task types together (such as CSrvSocketTask,
280 /// CSrvStatesTask etc.) and still have one implementation of CSrvTask.
281 template <class Derived>
282 class CSrvStatesTask : public virtual CSrvTask
283 {
284 protected:
285     /// Convenient typedef to use in state pointers.
286     typedef Derived Me;
287     struct State;
288     /// State-implementing method typedef.
289     typedef State (Me::*FStateFunc)(void);
290     /// Structure behaving identically to pointer to state-implementing method.
291     /// It's necessary because without it declaration of FStateFunc is
292     /// impossible.
293     struct State {
294         FStateFunc func;
295 
StateCSrvStatesTask::State296         State(void) : func(NULL) {}
StateCSrvStatesTask::State297         State(FStateFunc func) : func(func) {}
operator =CSrvStatesTask::State298         State& operator= (FStateFunc func_) {func = func_; return *this;}
operator ==CSrvStatesTask::State299         bool operator== (FStateFunc func_) {return func == func_;}
operator !=CSrvStatesTask::State300         bool operator!= (FStateFunc func_) {return func != func_;}
301     };
302 
303 
CSrvStatesTask(void)304     CSrvStatesTask(void)
305         : m_CurState(NULL)
306     {}
~CSrvStatesTask(void)307     virtual ~CSrvStatesTask(void)
308     {}
309     /// Sets current state of the machine.
SetState(State state)310     void SetState(State state)
311     {
312         m_CurState = state.func;
313     }
314 
315 protected:
316     /// Time slice execution for the state machine. Executes all
317     /// state-implementing functions until no state change is requested.
ExecuteSlice(TSrvThreadNum)318     virtual void ExecuteSlice(TSrvThreadNum /* thr_num */)
319     {
320 #if 0
321         for (;;) {
322             FStateFunc next_state = (((Me*)this)->*m_CurState)().func;
323             if (next_state == NULL)
324                 return;
325             m_CurState = next_state;
326 #if 0
327             SetRunnable();
328             return;
329 #endif
330         }
331 #else
332 // attempt to make NC more responsive
333 #ifndef _DEBUG
334         Uint8 start = s_CurJiffies;
335 #endif
336         for (;;) {
337             FStateFunc next_state = (((Me*)this)->*m_CurState)().func;
338             if (next_state == NULL)
339                 return;
340             m_CurState = next_state;
341 #ifndef _DEBUG
342             if (s_CurJiffies - start > 1) {
343                 SetRunnable();
344                 return;
345             }
346 #endif
347         }
348 #endif
349     }
350 
351 private:
352     /// Current state of the machine.
353     FStateFunc m_CurState;
354 };
355 
356 
357 class CSrvTransConsumer;
358 
359 struct SSrvConsList_Tag;
360 typedef intr::list_base_hook<intr::tag<SSrvConsList_Tag> >  TSrvConsListHook;
361 typedef intr::list<CSrvTransConsumer,
362                    intr::base_hook<TSrvConsListHook>,
363                    intr::constant_time_size<false> >        TSrvConsList;
364 
365 /// Special task that has some information to give to its consumers but first
366 /// this information should be obtained from somewhere. After construction
367 /// task is in "Initial" state. When first consumer asks to perform transition
368 /// task becomes runnable and consumer is put in the queue for later
369 /// notification. If any more consumers ask for transition they are put in the
370 /// queue for notification without disrupting transition process. When
371 /// ExecuteSlice() has finished doing transition state of the task becomes
372 /// "Final" and all consumers waiting in the queue are notified (become
373 /// runnable). Consumers asking for transition after that get notified right
374 /// away and state of the task never changes and remains "Final".
375 ///
376 /// Virtual inheritance from CSrvTask is used to allow derived classes to
377 /// combine several task types together (such as CSrvSocketTask,
378 /// CSrvStatesTask etc.) and still have one implementation of CSrvTask.
379 class CSrvTransitionTask : public virtual CSrvTask
380 {
381 public:
382     CSrvTransitionTask(void);
383     virtual ~CSrvTransitionTask(void);
384 
385     /// Requests task's state transition with the provided consumer to be
386     /// notified when transition is finished.
387     void RequestTransition(CSrvTransConsumer* consumer);
388     /// Cancel transition request from the provided consumer.
389     void CancelTransRequest(CSrvTransConsumer* consumer);
390 
391 protected:
392     /// Finish the transition process and change task's state from "Transition"
393     /// to "Final" with notification sent to all consumers. Method must be
394     /// called from inside ExecuteSlice() of this task.
395     void FinishTransition(void);
396     /// Checks if "Final" state was already reached.
397     bool IsTransStateFinal(void);
398 
399 private:
400     /// Three possible states of the task.
401     enum ETransState {
402         eState_Initial,
403         eState_Transition,
404         eState_Final
405     };
406 
407     /// Mutex to protect state changing.
408     CMiniMutex m_TransLock;
409     /// Current state of the task.
410     ETransState m_TransState;
411     /// Consumers waiting for transition to complete.
412     TSrvConsList m_Consumers;
413 };
414 
415 
416 /// Consumer of notification about transition completeness in
417 /// CSrvTransitionTask.
418 ///
419 /// Virtual inheritance from CSrvTask is used to allow derived classes to
420 /// combine several task types together (such as CSrvSocketTask,
421 /// CSrvStatesTask etc.) and still have one implementation of CSrvTask.
422 class CSrvTransConsumer : public virtual CSrvTask,
423                           public TSrvConsListHook
424 {
425 public:
426     CSrvTransConsumer(void);
427     virtual ~CSrvTransConsumer(void);
428 
429     /// Check if transition completeness was consumed. Derived classes must
430     /// use this method (instead of CSrvTransitionTask::IsTransStateFinal())
431     /// because it returns TRUE not only when transition has been finished
432     /// but also when this consumer was notified about that. Otherwise it will
433     /// be possible that other thread finished transition and this thread will
434     /// try to request transition on another task but it will unable to put
435     /// this task to m_Consumers list as it wasn't removed from m_Consumers
436     /// list of first task yet.
437     bool IsTransFinished(void);
438 
439 // Consider this section private as it's public for internal use only
440 // to minimize implementation-specific clutter in headers.
441 public:
442     /// Flag showing that transition was already consumed.
443     bool m_TransFinished;
444 };
445 
446 
447 END_NCBI_SCOPE
448 
449 #endif /* NETCACHE__SRV_TASKS__HPP */
450