XRootD
Loading...
Searching...
No Matches
XrdScheduler.cc
Go to the documentation of this file.
1/******************************************************************************/
2/* */
3/* X r d S c h e d u l e r . c c */
4/* */
5/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */
6/* Produced by Andrew Hanushevsky for Stanford University under contract */
7/* DE-AC02-76-SFO0515 with the Department of Energy */
8/* */
9/* This file is part of the XRootD software suite. */
10/* */
11/* XRootD is free software: you can redistribute it and/or modify it under */
12/* the terms of the GNU Lesser General Public License as published by the */
13/* Free Software Foundation, either version 3 of the License, or (at your */
14/* option) any later version. */
15/* */
16/* XRootD is distributed in the hope that it will be useful, but WITHOUT */
17/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */
18/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */
19/* License for more details. */
20/* */
21/* You should have received a copy of the GNU Lesser General Public License */
22/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */
23/* COPYING (GPL license). If not, see <http://www.gnu.org/licenses/>. */
24/* */
25/* The copyright holder's institutional names and contributor's names may not */
26/* be used to endorse or promote products derived from this software without */
27/* specific prior written permission of the institution or contributor. */
28/******************************************************************************/
29
30#include <cerrno>
31#include <fcntl.h>
32#include <signal.h>
33#include <cstdio>
34#include <sys/resource.h>
35#include <sys/stat.h>
36#include <sys/types.h>
37#include <sys/wait.h>
38#ifdef __APPLE__
39#include <AvailabilityMacros.h>
40#endif
41
42#include "Xrd/XrdJob.hh"
43#include "Xrd/XrdScheduler.hh"
44#include "XrdOuc/XrdOucTrace.hh" // For ABI compatibility only!
45#include "XrdSys/XrdSysError.hh"
47
48#define XRD_TRACE XrdTrace->
49#include "Xrd/XrdTrace.hh"
50
51/******************************************************************************/
52/* S t a t i c O b j e c t s */
53/******************************************************************************/
54
55 const char *XrdScheduler::TraceID = "Sched";
56
57/******************************************************************************/
58/* L o c a l C l a s s e s */
59/******************************************************************************/
60
62 {public:
64 pid_t pid;
65
66 XrdSchedulerPID(pid_t newpid, XrdSchedulerPID *prev)
67 {next = prev; pid = newpid;}
69 };
70
71/******************************************************************************/
72/* E x t e r n a l T h r e a d I n t e r f a c e s */
73/******************************************************************************/
74
75void *XrdStartReaper(void *carg)
76 {XrdScheduler *sp = (XrdScheduler *)carg;
77 sp->Reaper();
78 return (void *)0;
79 }
80
81void *XrdStartTSched(void *carg)
82 {XrdScheduler *sp = (XrdScheduler *)carg;
83 sp->TimeSched();
84 return (void *)0;
85 }
86
87void *XrdStartWorking(void *carg)
88 {XrdScheduler *sp = (XrdScheduler *)carg;
89 sp->Run();
90 return (void *)0;
91 }
92
93/******************************************************************************/
94/* C o n s t r u c t o r */
95/******************************************************************************/
96
98 int minw, int maxw, int maxi)
99 : XrdJob("underused thread monitor"),
100 XrdTraceOld(0), WorkAvail(0, "sched work")
101{
102 Boot(eP, tP, minw, maxw, maxi);
103}
104
105/******************************************************************************/
106
107
109 int minw, int maxw, int maxi)
110 : XrdJob("underused thread monitor"),
111 XrdTraceOld(tP), WorkAvail(0, "sched work")
112{
113
114// Invoke the main initialization function with a new style trace object
115//
116 Boot(eP, new XrdSysTrace("Xrd", eP->logger()), minw, maxw, maxi);
117}
118
119/******************************************************************************/
120
121// This constructor creates a self contained scheduler.
122//
123XrdScheduler::XrdScheduler(int minw, int maxw, int maxi)
124 : XrdJob("underused thread monitor"),
125 XrdTraceOld(0), WorkAvail(0, "sched work")
126{
128 int eFD;
129
130// Get a file descriptor mirroring standard error
131//
132#if ( defined(__linux__) || defined(__GNU__) ) && defined(F_DUPFD_CLOEXEC)
133 eFD = fcntl(STDERR_FILENO, F_DUPFD_CLOEXEC, 0);
134#else
135 eFD = dup(STDERR_FILENO);
136 fcntl(eFD, F_SETFD, FD_CLOEXEC);
137#endif
138
139// Now we need to get a logger object. We make this a real dumb one.
140//
141 Logger = new XrdSysLogger(eFD, 0);
142 XrdLog = new XrdSysError(Logger);
143
144// Now get a trace object
145//
146 XrdTrace = new XrdSysTrace("Xrd", Logger);
147 if (getenv("XRDDEBUG") != 0) XrdTrace->What = TRACE_SCHED;
148
149// Set remaining values. We do no use maximum possible threads here.
150//
151 Init(minw, maxw, maxi);
152}
153
154/******************************************************************************/
155/* Private: B o o t */
156/******************************************************************************/
157
158void XrdScheduler::Boot(XrdSysError *eP, XrdSysTrace *tP,
159 int minw, int maxw, int maxi)
160{
161// Perform common initialization
162//
163 XrdLog = eP;
164 XrdTrace = tP;
165 Init(minw, maxw, maxi);
166
167// Make sure we are using the maximum number of threads allowed (Linux only)
168//
169#if ( defined(__linux__) || defined(__GNU__) || (defined(__FreeBSD_kernel__) && defined(__GLIBC__)) ) && defined(RLIMIT_NPROC)
170
171 struct rlimit rlim;
172
173// First determine the absolute maximum we can have
174//
175 rlim_t theMax = MAX_SCHED_PROCS;
176 int pdFD, rdsz;
177 if ((pdFD = open("/proc/sys/kernel/pid_max", O_RDONLY)) >= 0)
178 {char pmBuff[32];
179 if ((rdsz = read(pdFD, pmBuff, sizeof(pmBuff))) > 0)
180 {rdsz = atoi(pmBuff);
181 if (rdsz < 16384) theMax = 16384; // This is unlikely
182 else if (rdsz < MAX_SCHED_PROCS)
183 theMax = static_cast<rlim_t>(rdsz-2000);
184 }
185 close(pdFD);
186 }
187
188// Get the resource thread limit and set to maximum. In Linux this may be -1
189// to indicate useless infnity, so we have to come up with a number, sigh.
190//
191 if (!getrlimit(RLIMIT_NPROC, &rlim))
192 {if (rlim.rlim_max == RLIM_INFINITY || rlim.rlim_max > theMax)
193 {rlim.rlim_cur = theMax;
194 setrlimit(RLIMIT_NPROC, &rlim);
195 } else {
196 if (rlim.rlim_cur != rlim.rlim_max)
197 {rlim.rlim_cur = rlim.rlim_max;
198 setrlimit(RLIMIT_NPROC, &rlim);
199 }
200 }
201 }
202
203// Readjust our internal maximum to be the actual maximum
204//
205 if (!getrlimit(RLIMIT_NPROC, &rlim))
206 {if (rlim.rlim_cur == RLIM_INFINITY || rlim.rlim_cur > theMax)
207 max_Workers = static_cast<int>(theMax);
208 else max_Workers = static_cast<int>(rlim.rlim_cur);
209 }
210#endif
211
212}
213
214/******************************************************************************/
215/* D e s t r u c t o r */
216/******************************************************************************/
217
218XrdScheduler::~XrdScheduler() // The scheduler is never deleted!
219{
220}
221
222/******************************************************************************/
223/* C a n c e l */
224/******************************************************************************/
225
227{
228 XrdJob *p, *pp = 0;
229
230// Lock the queue
231//
232 TimerMutex.Lock();
233
234// Find the matching job, if any
235//
236 p = TimerQueue;
237 while(p && p != jp) {pp = p; p = p->NextJob;}
238
239// Delete the job element
240//
241 if (p)
242 {if (pp) pp->NextJob = p->NextJob;
243 else TimerQueue = p->NextJob;
244 TRACE(SCHED, "time event " <<jp->Comment <<" cancelled");
245 }
246
247// All done
248//
249 TimerMutex.UnLock();
250}
251
252/******************************************************************************/
253/* D o I t */
254/******************************************************************************/
255
257{
258 int num_kill, num_idle;
259
260// Now check if there are too many idle threads (kill them if there are)
261//
262 if (!num_JobsinQ)
263 {DispatchMutex.Lock(); num_idle = idl_Workers; DispatchMutex.UnLock();
264 num_kill = num_idle - min_Workers;
265 TRACE(SCHED, num_Workers <<" threads; " <<num_idle <<" idle");
266 if (num_kill > 0)
267 {if (num_kill > 1) num_kill = num_kill/2;
268 SchedMutex.Lock();
269 num_Layoffs = num_kill;
270 while(num_kill--) WorkAvail.Post();
271 SchedMutex.UnLock();
272 }
273 }
274
275// Check if we should reschedule ourselves
276//
277 if (max_Workidl > 0) Schedule((XrdJob *)this, max_Workidl+time(0));
278}
279
280/******************************************************************************/
281/* F o r k */
282/******************************************************************************/
283
284// This entry exists solely so that we can start a reaper thread for processes
285//
286pid_t XrdScheduler::Fork(const char *id)
287{
288 static int retc, ReaperStarted = 0;
289 pthread_t tid;
290 pid_t pid;
291
292// Fork
293//
294 if ((pid = fork()) < 0)
295 {XrdLog->Emsg("Scheduler",errno,"fork to handle",id);
296 return pid;
297 }
298 if (!pid) return pid;
299
300// Obtain the status of the reaper thread.
301//
302 ReaperMutex.Lock();
303 firstPID = new XrdSchedulerPID(pid, firstPID);
304 retc = ReaperStarted;
305 ReaperStarted = 1;
306 ReaperMutex.UnLock();
307
308// Start the reaper thread if it has not started.
309//
310 if (!retc)
311 if ((retc = XrdSysThread::Run(&tid, XrdStartReaper, (void *)this,
312 0, "Process reaper")))
313 {XrdLog->Emsg("Scheduler", retc, "create reaper thread");
314 ReaperStarted = 0;
315 }
316
317 return pid;
318}
319
320/******************************************************************************/
321/* R e a p e r */
322/******************************************************************************/
323
325{
326 int status;
327 pid_t pid;
328 XrdSchedulerPID *tp, *ptp, *xtp;
329#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_5)
330 struct timespec ts = { 1, 0 };
331#else
332 sigset_t Sset;
333 int signum;
334
335// Set up for signal handling. Note: main() must block this signal at start)
336//
337 sigemptyset(&Sset);
338 sigaddset(&Sset, SIGCHLD);
339#endif
340
341// Wait for all outstanding children
342//
343 do {ReaperMutex.Lock();
344 tp = firstPID; ptp = 0;
345 while(tp)
346 {do {pid = waitpid(tp->pid, &status, WNOHANG);}
347 while (pid < 0 && errno == EINTR);
348 if (pid > 0)
349 {if (TRACING(TRACE_SCHED)) traceExit(pid, status);
350 xtp = tp; tp = tp->next;
351 if (ptp) ptp->next = tp;
352 else firstPID = tp;
353 delete xtp;
354 } else {ptp = tp; tp = tp->next;}
355 }
356 ReaperMutex.UnLock();
357#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_5)
358 // Mac OS X sigwait() is broken on <= 10.4.
359 } while (nanosleep(&ts, 0) <= 0);
360#else
361 } while(sigwait(&Sset, &signum) >= 0);
362#endif
363 return (void *)0;
364}
365
366/******************************************************************************/
367/* R u n */
368/******************************************************************************/
369
371{
372 int waiting;
373 XrdJob *jp;
374
375// Wait for work then do it (an endless task for a worker thread)
376//
377 do {do {DispatchMutex.Lock(); idl_Workers++;DispatchMutex.UnLock();
378 WorkAvail.Wait();
379 DispatchMutex.Lock();waiting = --idl_Workers;DispatchMutex.UnLock();
380 SchedMutex.Lock();
381 if ((jp = WorkFirst))
382 {if (!(WorkFirst = jp->NextJob)) WorkLast = 0;
383 if (num_JobsinQ) num_JobsinQ--;
384 else XrdLog->Emsg("Scheduler","Job queue count underflow!");
385 } else {
386 num_JobsinQ = 0;
387 if (num_Layoffs > 0)
388 {num_Layoffs--;
389 if (waiting)
390 {num_TDestroy++; num_Workers--;
391 TRACE(SCHED, "terminating thread; workers=" <<num_Workers);
392 SchedMutex.UnLock();
393 return;
394 }
395 }
396 }
397 SchedMutex.UnLock();
398 } while(!jp);
399
400 // Check if we should hire a new worker (we always want 1 idle thread)
401 // before running this job.
402 //
403 if (!waiting) hireWorker();
404 if (TRACING(TRACE_SCHED) && *(jp->Comment) != '.')
405 {TRACE(SCHED, "running " <<jp->Comment <<" inq=" <<num_JobsinQ);}
406 jp->DoIt();
407 } while(1);
408}
409
410/******************************************************************************/
411/* S c h e d u l e */
412/******************************************************************************/
413
415{
416// Lock down our data area
417//
418 SchedMutex.Lock();
419
420// Place the request on the queue and broadcast it
421//
422 jp->NextJob = 0;
423 if (WorkFirst)
424 {WorkLast->NextJob = jp;
425 WorkLast = jp;
426 } else {
427 WorkFirst = jp;
428 WorkLast = jp;
429 }
430 WorkAvail.Post();
431
432// Calculate statistics
433//
434 num_Jobs++;
435 num_JobsinQ++;
436 if (num_JobsinQ > max_QLength) max_QLength = num_JobsinQ;
437
438// Unlock the data area and return
439//
440 SchedMutex.UnLock();
441}
442
443/******************************************************************************/
444
445void XrdScheduler::Schedule(int numjobs, XrdJob *jfirst, XrdJob *jlast)
446{
447
448// Lock down our data area
449//
450 SchedMutex.Lock();
451
452// Place the request list on the queue
453//
454 jlast->NextJob = 0;
455 if (WorkFirst)
456 {WorkLast->NextJob = jfirst;
457 WorkLast = jlast;
458 } else {
459 WorkFirst = jfirst;
460 WorkLast = jlast;
461 }
462
463// Calculate statistics
464//
465 num_Jobs += numjobs;
466 num_JobsinQ += numjobs;
467 if (num_JobsinQ > max_QLength) max_QLength = num_JobsinQ;
468
469// Indicate number of jobs to work on
470//
471 while(numjobs--) WorkAvail.Post();
472
473// Unlock the data area and return
474//
475 SchedMutex.UnLock();
476}
477
478/******************************************************************************/
479
480void XrdScheduler::Schedule(XrdJob *jp, time_t atime)
481{
482 XrdJob *pp = 0, *p;
483
484// Cancel this event, if scheduled
485//
486 Cancel(jp);
487
488// Lock the queue
489//
490 if (TRACING(TRACE_SCHED) && *(jp->Comment) != '.')
491 {TRACE(SCHED, "scheduling " <<jp->Comment <<" in " <<atime-time(0) <<" seconds");}
492 jp->SchedTime = atime;
493 TimerMutex.Lock();
494
495// Find the insertion point for the work element
496//
497 p = TimerQueue;
498 while(p && p->SchedTime <= atime) {pp = p; p = p->NextJob;}
499
500// Insert the job element
501//
502 jp->NextJob = p;
503 if (pp) pp->NextJob = jp;
504 else {TimerQueue = jp; TimerRings.Signal();}
505
506// All done
507//
508 TimerMutex.UnLock();
509}
510
511/******************************************************************************/
512/* s e t P a r m s */
513/******************************************************************************/
514
515void XrdScheduler::setParms(int minw, int maxw, int avlw, int maxi, int once)
516{
517 static int isSet = 0;
518
519// Lock the data area and check for 1-time set
520//
521 SchedMutex.Lock();
522 if (once && isSet) {SchedMutex.UnLock(); return;}
523 isSet = 1;
524
525// get a consistent view of all the values
526//
527 if (maxw <= 0) maxw = max_Workers;
528 if (minw < 0) minw = min_Workers;
529 if (minw > maxw) minw = maxw;
530 if (avlw < 0) avlw = maxw/4*3;
531 else if (avlw > maxw) avlw = maxw;
532
533// Set the values
534//
535 min_Workers = minw;
536 max_Workers = maxw;
537 stk_Workers = maxw - avlw;
538 if (maxi >=0) max_Workidl = maxi;
539
540// Unlock the data area
541//
542 SchedMutex.UnLock();
543
544// If we have an idle interval, schedule the idle check
545//
546 if (maxi > 0)
547 {Cancel((XrdJob *)this);
548 Schedule((XrdJob *)this, (time_t)maxi+time(0));
549 }
550
551// Debug the info
552//
553 TRACE(SCHED,"Set min_Workers=" <<min_Workers <<" max_Workers=" <<max_Workers);
554 TRACE(SCHED,"Set stk_Workers=" <<stk_Workers <<" max_Workidl=" <<max_Workidl);
555}
556
557/******************************************************************************/
558/* S t a r t */
559/******************************************************************************/
560
561void XrdScheduler::Start() // Serialized one time call!
562{
563 int retc, numw;
564 pthread_t tid;
565
566// Provide ABI compatibility for XrdOucTrace which is deprecated!
567//
568 if (getenv("XRDDEBUG") != 0) XrdTrace->What = TRACE_SCHED;
569 else if (XrdTraceOld) XrdTrace->What |= XrdTraceOld->What;
570
571// Start a time based scheduler
572//
573 if ((retc = XrdSysThread::Run(&tid, XrdStartTSched, (void *)this,
574 XRDSYSTHREAD_BIND, "Time scheduler")))
575 XrdLog->Emsg("Scheduler", retc, "create time scheduler thread");
576
577// If we an idle interval, schedule the idle check
578//
579 if (max_Workidl > 0) Schedule((XrdJob *)this, (time_t)max_Workidl+time(0));
580
581// Start 1/3 of the minimum number of threads
582//
583 if (!(numw = min_Workers/3)) numw = 2;
584 while(numw--) hireWorker(0);
585
586// Unlock the data area
587//
588 TRACE(SCHED, "Starting with " <<num_Workers <<" workers" );
589}
590
591/******************************************************************************/
592/* S t a t s */
593/******************************************************************************/
594
595int XrdScheduler::Stats(char *buff, int blen, int do_sync)
596{
597 int cnt_Jobs, cnt_JobsinQ, xam_QLength, cnt_Workers, cnt_idl;
598 int cnt_TCreate, cnt_TDestroy, cnt_Limited;
599 static char statfmt[] = "<stats id=\"sched\"><jobs>%d</jobs>"
600 "<inq>%d</inq><maxinq>%d</maxinq>"
601 "<threads>%d</threads><idle>%d</idle>"
602 "<tcr>%d</tcr><tde>%d</tde>"
603 "<tlimr>%d</tlimr></stats>";
604
605// If only length wanted, do so
606//
607 if (!buff) return sizeof(statfmt) + 16*8;
608
609// Get values protected by the Dispatch lock (avoid lock if no sync needed)
610//
611 if (do_sync) DispatchMutex.Lock();
612 cnt_idl = idl_Workers;
613 if (do_sync) DispatchMutex.UnLock();
614
615// Get values protected by the Scheduler lock (avoid lock if no sync needed)
616//
617 if (do_sync) SchedMutex.Lock();
618 cnt_Workers = num_Workers;
619 cnt_Jobs = num_Jobs;
620 cnt_JobsinQ = num_JobsinQ;
621 xam_QLength = max_QLength;
622 cnt_TCreate = num_TCreate;
623 cnt_TDestroy= num_TDestroy;
624 cnt_Limited = num_Limited;
625 if (do_sync) SchedMutex.UnLock();
626
627// Format the stats and return them
628//
629 return snprintf(buff, blen, statfmt, cnt_Jobs, cnt_JobsinQ, xam_QLength,
630 cnt_Workers, cnt_idl, cnt_TCreate, cnt_TDestroy,
631 cnt_Limited);
632}
633
634/******************************************************************************/
635/* T i m e S c h e d */
636/******************************************************************************/
637
639{
640 XrdJob *jp;
641 int wtime;
642
643// Continuous loop until we find some work here
644//
645 do {TimerMutex.Lock();
646 if (TimerQueue) wtime = TimerQueue->SchedTime-time(0);
647 else wtime = 60*60;
648 if (wtime > 0)
649 {TimerMutex.UnLock();
650 TimerRings.Wait(wtime);
651 } else {
652 jp = TimerQueue;
653 TimerQueue = jp->NextJob;
654 Schedule(jp);
655 TimerMutex.UnLock();
656 }
657 } while(1);
658}
659
660/******************************************************************************/
661/* P r i v a t e M e t h o d s */
662/******************************************************************************/
663/******************************************************************************/
664/* h i r e W o r k e r */
665/******************************************************************************/
666
667void XrdScheduler::hireWorker(int dotrace)
668{
669 pthread_t tid;
670 int retc;
671
672// First check if we reached the maximum number of workers
673//
674 SchedMutex.Lock();
675 if (num_Workers >= max_Workers)
676 {num_Limited++;
677 if ((num_Limited & 4095) == 1)
678 XrdLog->Emsg("Scheduler","Thread limit has been reached!");
679 SchedMutex.UnLock();
680 return;
681 }
682 num_Workers++;
683 num_TCreate++;
684 SchedMutex.UnLock();
685
686// Start a new thread. We do this without the schedMutex to avoid hang-ups. If
687// we can't start a new thread, we recalculate the maximum number we can.
688//
689 retc = XrdSysThread::Run(&tid, XrdStartWorking, (void *)this, 0, "Worker");
690
691// Now check the results and correct if we couldn't start the thread
692//
693 if (retc)
694 {XrdLog->Emsg("Scheduler", retc, "create worker thread");
695 SchedMutex.Lock();
696 num_Workers--;
697 num_TCreate--;
698 max_Workers = num_Workers;
699 min_Workers = (max_Workers/10 ? max_Workers/10 : 1);
700 stk_Workers = max_Workers/4*3;
701 SchedMutex.UnLock();
702 } else if (dotrace) TRACE(SCHED, "Now have " <<num_Workers <<" workers" );
703}
704
705/******************************************************************************/
706/* I n i t */
707/******************************************************************************/
708
709void XrdScheduler::Init(int minw, int maxw, int maxi)
710{
711 min_Workers = minw;
712 max_Workers = maxw;
713 max_Workidl = maxi;
714 num_Workers = 0;
715 num_JobsinQ = 0;
716 stk_Workers = maxw - (maxw/4*3);
717 idl_Workers = 0;
718 num_Jobs = 0;
719 max_QLength = 0;
720 num_TCreate = 0;
721 num_TDestroy= 0;
722 num_Layoffs = 0;
723 num_Limited = 0;
724 firstPID = 0;
725 WorkFirst = WorkLast = TimerQueue = 0;
726}
727
728/******************************************************************************/
729/* t r a c e E x i t */
730/******************************************************************************/
731
732void XrdScheduler::traceExit(pid_t pid, int status)
733{ const char *why;
734 int retc;
735
736 if (WIFEXITED(status))
737 {retc = WEXITSTATUS(status);
738 why = " exited with rc=";
739 } else if (WIFSIGNALED(status))
740 {retc = WTERMSIG(status);
741 why = " killed with signal ";
742 } else {retc = 0;
743 why = " changed state ";
744 }
745 TRACE(SCHED, "Process " <<pid <<why <<retc);
746}
static XrdSysLogger Logger
XrdSysError XrdLog(0, "")
int fcntl(int fd, int cmd,...)
#define close(a)
Definition XrdPosix.hh:43
#define open
Definition XrdPosix.hh:71
#define read(a, b, c)
Definition XrdPosix.hh:77
void * XrdStartWorking(void *carg)
void * XrdStartReaper(void *carg)
void * XrdStartTSched(void *carg)
#define MAX_SCHED_PROCS
#define XRDSYSTHREAD_BIND
#define TRACE_SCHED
Definition XrdTrace.hh:42
#define TRACE(act, x)
Definition XrdTrace.hh:63
#define TRACING(x)
Definition XrdTrace.hh:70
XrdJob * NextJob
Definition XrdJob.hh:46
friend class XrdScheduler
Definition XrdJob.hh:44
XrdJob(const char *desc="")
Definition XrdJob.hh:51
virtual void DoIt()=0
const char * Comment
Definition XrdJob.hh:47
XrdSchedulerPID * next
XrdSchedulerPID(pid_t newpid, XrdSchedulerPID *prev)
int Stats(char *buff, int blen, int do_sync=0)
void Schedule(XrdJob *jp)
void setParms(int minw, int maxw, int avlt, int maxi, int once=0)
void Cancel(XrdJob *jp)
void * Reaper()
pid_t Fork(const char *id)
XrdSysLogger * logger(XrdSysLogger *lp=0)
static int Run(pthread_t *, void *(*proc)(void *), void *arg, int opts=0, const char *desc=0)