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