1/*
2 * Copyright (c) 2010 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1.  Redistributions of source code must retain the above copyright
11 *     notice, this list of conditions and the following disclaimer.
12 * 2.  Redistributions in binary form must reproduce the above copyright
13 *     notice, this list of conditions and the following disclaimer in the
14 *     documentation and/or other materials provided with the distribution.
15 * 3.  Neither the name of Apple Inc. ("Apple") nor the names of its
16 *     contributors may be used to endorse or promote products derived from
17 *     this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
20 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 *
30 * Portions of this software have been released under the following terms:
31 *
32 * (c) Copyright 1989-1993 OPEN SOFTWARE FOUNDATION, INC.
33 * (c) Copyright 1989-1993 HEWLETT-PACKARD COMPANY
34 * (c) Copyright 1989-1993 DIGITAL EQUIPMENT CORPORATION
35 *
36 * To anyone who acknowledges that this file is provided "AS IS"
37 * without any express or implied warranty:
38 * permission to use, copy, modify, and distribute this file for any
39 * purpose is hereby granted without fee, provided that the above
40 * copyright notices and this notice appears in all source code copies,
41 * and that none of the names of Open Software Foundation, Inc., Hewlett-
42 * Packard Company or Digital Equipment Corporation be used
43 * in advertising or publicity pertaining to distribution of the software
44 * without specific, written prior permission.  Neither Open Software
45 * Foundation, Inc., Hewlett-Packard Company nor Digital
46 * Equipment Corporation makes any representations about the suitability
47 * of this software for any purpose.
48 *
49 * Copyright (c) 2007, Novell, Inc. All rights reserved.
50 * Redistribution and use in source and binary forms, with or without
51 * modification, are permitted provided that the following conditions
52 * are met:
53 *
54 * 1.  Redistributions of source code must retain the above copyright
55 *     notice, this list of conditions and the following disclaimer.
56 * 2.  Redistributions in binary form must reproduce the above copyright
57 *     notice, this list of conditions and the following disclaimer in the
58 *     documentation and/or other materials provided with the distribution.
59 * 3.  Neither the name of Novell Inc. nor the names of its contributors
60 *     may be used to endorse or promote products derived from this
61 *     this software without specific prior written permission.
62 *
63 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
64 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
65 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
66 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY
67 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
68 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
69 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
70 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
71 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
72 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
73 *
74 * @APPLE_LICENSE_HEADER_END@
75 */
76
77/*
78**
79**  NAME
80**
81**      rpctimer.c
82**
83**  FACILITY:
84**
85**      Remote Procedure Call (RPC)
86**
87**  ABSTRACT:
88**
89**  Definitions of types/constants internal to RPC facility and common
90**  to all RPC components.
91**
92**
93*/
94
95#include <commonp.h>
96
97
98/* ========================================================================= */
99
100EXTERNAL rpc_clock_t rpc_g_clock_curr;
101GLOBAL boolean32 rpc_g_long_sleep;
102
103INTERNAL rpc_timer_p_t running_list;
104INTERNAL rpc_mutex_t   rpc_g_timer_mutex;
105INTERNAL rpc_cond_t    rpc_g_timer_cond;
106INTERNAL rpc_clock_t   rpc_timer_high_trigger;
107INTERNAL rpc_clock_t   rpc_timer_cur_trigger;
108INTERNAL rpc_timer_p_t rpc_timer_head, rpc_timer_tail;
109INTERNAL unsigned32    stop_timer;
110INTERNAL dcethread* timer_task;
111INTERNAL boolean timer_task_running = false;
112INTERNAL boolean timer_task_was_running = false;
113
114INTERNAL void timer_loop (void);
115
116INTERNAL void rpc__timer_set_int (
117        rpc_timer_p_t            /*t*/,
118        rpc_timer_proc_p_t       /*proc*/,
119        dce_pointer_t                /*parg*/,
120        rpc_clock_t              /*freq*/
121    );
122
123/*
124 * Mutex lock macros
125 */
126
127#define RPC_TIMER_LOCK_INIT(junk)   RPC_MUTEX_INIT (rpc_g_timer_mutex)
128#define RPC_TIMER_LOCK(junk)        RPC_MUTEX_LOCK (rpc_g_timer_mutex)
129#define RPC_TIMER_UNLOCK(junk)      RPC_MUTEX_UNLOCK (rpc_g_timer_mutex)
130#define RPC_TIMER_LOCK_ASSERT(junk) RPC_MUTEX_LOCK_ASSERT(rpc_g_timer_mutex)
131#define RPC_TIMER_COND_INIT(junk)   RPC_COND_INIT (rpc_g_timer_cond, rpc_g_timer_mutex)
132
133/* ========================================================================= */
134
135
136/*
137 * T I M E R _ L O O P
138 *
139 * Periodically get the new clock time and process any ready timer events.
140 */
141
142#ifndef NO_RPC_TIMER_THREAD
143
144INTERNAL void timer_loop(void)
145{
146    RPC_TIMER_LOCK(0);
147
148    while (!stop_timer)
149    {
150        rpc_clock_t next;
151        struct timespec next_ts;
152        rpc_clock_t max_step;
153
154	/*
155	 * It would be real nice if we could figure out a way to get
156	 * the system time global variable mapped read-only into our
157	 * address space to avoid the !@#$% gettimeofday syscall overhead.
158	 */
159        rpc__clock_update();
160        next = rpc__timer_callout();
161	rpc_g_long_sleep = ( next > RPC_CLOCK_SEC(1) );
162
163        /*
164         * wake up at least once every 50 seconds, so we don't confuse
165         * the underlying rpc__clock_update() code.
166         */
167        max_step = RPC_CLOCK_SEC(50);
168        if (next == 0 || next > max_step)
169            next = max_step;
170
171        if (next > 10)
172        {
173            RPC_DBG_PRINTF(rpc_e_dbg_timer, 5,
174                ("(timer_loop) next event in %ld seconds\n", next/RPC_C_CLOCK_HZ));
175        }
176        rpc_timer_cur_trigger = rpc_g_clock_curr + next;
177        rpc__clock_timespec (rpc_timer_cur_trigger, &next_ts);
178        RPC_COND_TIMED_WAIT(rpc_g_timer_cond, rpc_g_timer_mutex, &next_ts);
179    }
180
181    RPC_TIMER_UNLOCK(0);
182}
183
184#endif /* NO_RPC_TIMER_THREAD */
185
186INTERNAL void rpc__timer_prod
187(
188        rpc_clock_t trigger
189)
190{
191
192    RPC_DBG_PRINTF(rpc_e_dbg_timer, 5, (
193        "(rpc__timer_prod) timer backup; old %ld, new %ld\n",
194        rpc_timer_cur_trigger, trigger
195        ));
196    /*
197     * forestall "double pokes", which appear to
198     * happen occasionally.
199     */
200    rpc_timer_cur_trigger = trigger;
201    RPC_COND_SIGNAL(rpc_g_timer_cond, rpc_g_timer_mutex);
202
203}
204
205
206/*
207 **  R P C _ _ T I M E R _ I N I T
208 **
209 **  Initialize the timer package.
210 **/
211
212PRIVATE void rpc__timer_init(void)
213{
214    RPC_TIMER_LOCK_INIT (0);
215    RPC_TIMER_COND_INIT (0);
216
217    running_list            = NULL;
218    rpc_timer_high_trigger  = 0;
219    rpc_timer_head          = NULL;
220    rpc_timer_tail          = NULL;
221    rpc_g_clock_curr        = 0;
222    rpc_g_long_sleep	    = 0;
223    timer_task_running      = false;
224    timer_task_was_running  = false;
225
226    /*
227     * Initialize the current time...
228     */
229    rpc__clock_update();
230
231#ifndef NO_RPC_TIMER_THREAD
232    stop_timer              = 0;
233    timer_task_running      = true;
234    dcethread_create_throw (
235        &timer_task,                            /* new thread    */
236        NULL,                   /* attributes    */
237        (dcethread_startroutine)timer_loop,    /* start routine */
238        NULL);                 /* arguments     */
239
240#endif /* NO_RPC_TIMER_THREAD */
241}
242
243#ifdef ATFORK_SUPPORTED
244/*
245 **  R P C _ _ T I M E R _ F O R K _ H A N D L E R
246 **
247 **  Handle timer related fork issues.
248 **/
249
250PRIVATE void rpc__timer_fork_handler
251(
252    rpc_fork_stage_id_t stage
253)
254{
255#ifndef NO_RPC_TIMER_THREAD
256    switch ((int)stage)
257    {
258        case RPC_C_PREFORK:
259            RPC_TIMER_LOCK(0);
260            timer_task_was_running = false;
261            if (timer_task_running)
262            {
263                stop_timer = 1;
264                rpc__timer_prod(0);
265                RPC_TIMER_UNLOCK(0);
266                dcethread_join_throw(timer_task, NULL);
267                RPC_TIMER_LOCK(0);
268                /* Why was this ever done?  It doesn't make any sense to
269                   perform a detach right after a join
270                DCETHREAD_TRY
271                {
272                    dcethread_detach_throw(timer_task);
273                }
274                DCETHREAD_CATCH(dcethread_use_error_e)
275                {
276                }
277		DCETHREAD_CATCH(dcethread_badparam_e)
278		{
279		}
280                DCETHREAD_ENDTRY; */
281                timer_task_was_running = true;
282                timer_task_running = false;
283            }
284            break;
285
286        case RPC_C_POSTFORK_PARENT:
287            if (timer_task_was_running)
288            {
289                timer_task_was_running = false;
290                timer_task_running = true;
291                stop_timer = 0;
292                dcethread_create_throw (
293                    &timer_task,                          /* new thread    */
294                    NULL,                 /* attributes    */
295                    (dcethread_startroutine) timer_loop,  /* start routine */
296                    NULL);               /* arguments     */
297            }
298            RPC_TIMER_UNLOCK(0);
299            break;
300
301        case RPC_C_POSTFORK_CHILD:
302            timer_task_was_running = false;
303            timer_task_running = false;
304            stop_timer = 0;
305            RPC_TIMER_UNLOCK(0);
306            break;
307    }
308#endif /* NO_RPC_TIMER_THREAD */
309}
310#endif /* ATFORK_SUPPORTED */
311
312
313/*
314** R P C _ _ T I M E R _ S E T
315**
316** Lock timer lock, add timer to timer callout queue, and unlock timer lock.
317**
318*/
319
320PRIVATE void rpc__timer_set
321(
322    rpc_timer_p_t           t,
323    rpc_timer_proc_p_t      proc,
324    dce_pointer_t               parg,
325    rpc_clock_t             freq
326)
327{
328    RPC_TIMER_LOCK (0);
329    rpc__timer_set_int (t, proc, parg, freq);
330    RPC_TIMER_UNLOCK (0);
331}
332
333/*
334**  R P C _ _ T I M E R _ S E T _ I N T
335**
336**  Insert a routine to be called from the rpc_timer periodic callout queue.
337**  The entry address, a single argument to be passed when the routine is
338**  called,  together with the frequency with which the routine is to be
339**  called are inserted into a rpc_timer_t structure provided by the
340**  caller and pointed to by 't', which is placed in the queue.
341*/
342
343INTERNAL void rpc__timer_set_int
344(
345    rpc_timer_p_t           t,
346    rpc_timer_proc_p_t      proc,
347    dce_pointer_t               parg,
348    rpc_clock_t             freq
349)
350{
351    rpc_timer_p_t       list_ptr, prev_ptr;
352
353    /*
354     * Insert the routine calling address, the argument to be passed and
355     * the frequency into the rpc_timer_t structure.
356     */
357
358    t->proc = proc;
359    t->parg = parg;
360    t->frequency = freq;
361    t->trigger = freq + rpc_g_clock_curr;
362
363    /*
364     * Insert the rpc_timer_t structure sent by the caller into the
365     * rpc_timer queue, by order of the frequency with which it is to be
366     * called.
367     */
368    if (rpc_timer_head != NULL && t->trigger >= rpc_timer_high_trigger)
369    {
370        rpc_timer_tail->next = t;
371        rpc_timer_tail = t;
372        t->next = 0;
373        rpc_timer_high_trigger = t->trigger;
374        return;
375    }
376
377    /*
378     * Handle the case in which the element gets inserted somewhere
379     * into the middle of the list.
380     */
381
382    prev_ptr = NULL;
383    for (list_ptr = rpc_timer_head; list_ptr; list_ptr = list_ptr->next )
384    {
385        if (t->trigger < list_ptr->trigger)
386        {
387            if (list_ptr == rpc_timer_head)
388            {
389                t->next = rpc_timer_head;
390                rpc_timer_head = t;
391                /*
392                 * If the next time the timer thread will wake up is
393                 * in the future, prod it.
394                 */
395                if (rpc_timer_cur_trigger > t->trigger)
396                    rpc__timer_prod(t->trigger);
397            }
398            else
399            {
400                assert(prev_ptr != NULL);
401                prev_ptr->next = t;
402                t->next = list_ptr;
403            }
404            return;
405        }
406        prev_ptr = list_ptr;
407    }
408
409    /*
410     * The only way to get this far is when the head pointer is NULL.  Either
411     * we just started up, or possibly there's been no RPC activity and all the
412     * monitors have removed themselves.
413     */
414    assert (rpc_timer_head == NULL);
415
416    rpc_timer_head = rpc_timer_tail = t;
417    t->next = NULL;
418    rpc_timer_high_trigger = t->trigger;
419    if (rpc_timer_cur_trigger > t->trigger)
420        rpc__timer_prod(t->trigger);
421}
422
423/*
424 **  R P C _ _ T I M E R _ A D J U S T
425 **
426 **  Search the rpc_timer queue for the rpc_timer_t structure pointed to by
427 **   't'.  Then modify the 'frequency' attribute.
428 **/
429
430PRIVATE void rpc__timer_adjust
431(
432    rpc_timer_p_t           t,
433    rpc_clock_t             frequency
434)
435{
436    rpc_timer_p_t       ptr;
437
438    /*
439     * First see if the monitor being adjusted is currently running.
440     * If so, just reset the frequency field, when the monitor is rescheduled
441     * it will be queued in the proper place.
442     */
443
444    RPC_TIMER_LOCK (0);
445    for (ptr = running_list; ptr != NULL; ptr = ptr->next)
446    {
447        if (t == ptr)
448        {
449            t->frequency = frequency;
450            RPC_TIMER_UNLOCK (0);
451            return;
452        }
453    }
454
455    RPC_TIMER_UNLOCK (0);
456    /*
457     * Otherwise, we need to remove the monitor from its current position
458     * in the queue, adjust the frequency, and then replace it in the correct
459     * position.
460     */
461
462    rpc__timer_clear (t);
463    t->frequency = frequency;
464    rpc__timer_set (t, t->proc, t->parg, t->frequency);
465}
466
467/*
468 ** R P C _ _ T I M E R _ C L E A R
469 **
470 **  Remove an rpc_timer_t structure from the rpc_timer scheduling queue.
471 **/
472
473PRIVATE void rpc__timer_clear
474(
475    rpc_timer_p_t           t
476)
477{
478    rpc_timer_p_t       list_ptr, prev = NULL;
479
480    RPC_TIMER_LOCK (0);
481
482    /*
483     * First see if the monitor being cleared is currently running.
484     * If so, remove it from the running list.
485     */
486
487    for (list_ptr = running_list; list_ptr; prev = list_ptr, list_ptr = list_ptr->next)
488    {
489        if (list_ptr == t)
490        {
491            if (prev == NULL)
492                running_list = running_list->next;
493            else
494                prev->next = list_ptr->next;
495            RPC_TIMER_UNLOCK (0);
496            return;
497        }
498    }
499
500    /*
501     * Next, see if the specified timer element is in the list.
502     */
503
504    prev = NULL;
505    for (list_ptr = rpc_timer_head; list_ptr;
506        prev = list_ptr, list_ptr = list_ptr->next)
507    {
508        if (list_ptr == t)
509        {
510            /*
511             * If this element was at the head of the list...
512             */
513            if (t == rpc_timer_head)
514            {
515                /*
516                 * !!! perhaps need to inform timer thread that next event
517                 * is a little further off?
518                 */
519                rpc_timer_head = t->next;
520            }
521            else
522            {
523                /*
524                 * Unlink element from list.  If the element was at the end
525                 * of the list, reset the tail pointer and high trigger value.
526                 */
527                prev->next = t->next;
528                if (t->next == NULL)
529                {
530                    rpc_timer_tail = prev;
531                    rpc_timer_high_trigger = prev->trigger;
532                }
533            }
534            RPC_TIMER_UNLOCK (0);
535            return;
536        }
537    }
538
539    RPC_TIMER_UNLOCK (0);
540}
541
542/*
543**  R P C _ _ T I M E R _ C A L L O U T
544**
545**  Make a pass through the rpc_timer_t periodic callout queue.
546**  Make a call to any routines which are ready to run at this time.
547**  Return the amount of time until the next routine is scheduled
548**  to be run.  Return 0 if the queue is empty.
549**  The mutex, RPC_TIMER_LOCK, is LOCKED while this routine is running
550**  and while any routines which may be calld are running.
551**
552**  the parameter NEXT is set to the "next" time to wake up, if any
553**  (suitable for passing to RPC_COND_TIMED_WAIT).
554**/
555
556PRIVATE rpc_clock_t rpc__timer_callout (void)
557{
558    rpc_clock_t      ret_val;
559    rpc_timer_p_t   ptr, prev;
560
561#if 0
562    RPC_TIMER_LOCK (0);
563#endif
564
565    RPC_TIMER_LOCK_ASSERT(0);
566
567    /*
568     * Go through list, running routines which have trigger counts which
569     * have expired.
570     */
571
572    while (rpc_timer_head && rpc_timer_head->trigger <= rpc_g_clock_curr)
573    {
574        ptr = rpc_timer_head;
575        rpc_timer_head = rpc_timer_head->next;
576        ptr->next = running_list;
577        running_list = ptr;
578
579        RPC_TIMER_UNLOCK (0);
580        (*ptr->proc) (ptr->parg);
581        RPC_TIMER_LOCK (0);
582
583        /*
584         * We need to handle two situations here depending on whether
585         * the monitor routine has deleted itself from the timer queue.
586         * If so, we'll know this from the fact that it will be gone
587         * from the running list.  If not, we need to update the head
588         * pointer here, and reschedule the current monitor.
589         */
590
591        if (ptr == running_list)
592        {
593            running_list = running_list->next;
594            rpc__timer_set_int (ptr, ptr->proc, ptr->parg, ptr->frequency);
595        }
596        else
597        {
598            for (prev = running_list; prev; prev = prev->next)
599            {
600                if (prev->next == ptr)
601                {
602                    prev->next = ptr->next;
603
604                    rpc__timer_set_int
605                        (ptr, ptr->proc, ptr->parg, ptr->frequency);
606
607                }
608            }
609        }
610    }
611
612    ret_val = (rpc_timer_head == NULL) ? 0
613        : (rpc_timer_head->trigger - rpc_g_clock_curr);
614
615    RPC_TIMER_LOCK_ASSERT(0);
616    return (ret_val);
617}
618