|
|
1.1 root 1: /*
2: * Copyright (C) 2006 Michael Brown <[email protected]>.
3: *
4: * This program is free software; you can redistribute it and/or
5: * modify it under the terms of the GNU General Public License as
6: * published by the Free Software Foundation; either version 2 of the
7: * License, or any later version.
8: *
9: * This program is distributed in the hope that it will be useful, but
10: * WITHOUT ANY WARRANTY; without even the implied warranty of
11: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12: * General Public License for more details.
13: *
14: * You should have received a copy of the GNU General Public License
15: * along with this program; if not, write to the Free Software
16: * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17: */
18:
19: FILE_LICENCE ( GPL2_OR_LATER );
20:
21: #include <stddef.h>
22: #include <ipxe/timer.h>
23: #include <ipxe/list.h>
24: #include <ipxe/process.h>
25: #include <ipxe/init.h>
26: #include <ipxe/retry.h>
27:
28: /** @file
29: *
30: * Retry timers
31: *
32: * A retry timer is a binary exponential backoff timer. It can be
33: * used to build automatic retransmission into network protocols.
34: *
35: * This implementation of the timer is designed to satisfy RFC 2988
36: * and therefore be usable as a TCP retransmission timer.
37: *
38: *
39: */
40:
41: /* The theoretical minimum that the algorithm in stop_timer() can
42: * adjust the timeout back down to is seven ticks, so set the minimum
43: * timeout to at least that value for the sake of consistency.
44: */
45: #define MIN_TIMEOUT 7
46:
47: /** List of running timers */
48: static LIST_HEAD ( timers );
49:
50: /**
51: * Start timer
52: *
53: * @v timer Retry timer
54: *
55: * This starts the timer running with the current timeout value. If
56: * stop_timer() is not called before the timer expires, the timer will
57: * be stopped and the timer's callback function will be called.
58: */
59: void start_timer ( struct retry_timer *timer ) {
60: if ( ! timer->running ) {
61: list_add ( &timer->list, &timers );
62: ref_get ( timer->refcnt );
63: }
64: timer->start = currticks();
65: timer->running = 1;
66:
67: /* 0 means "use default timeout" */
68: if ( timer->min_timeout == 0 )
69: timer->min_timeout = DEFAULT_MIN_TIMEOUT;
70: /* We must never be less than MIN_TIMEOUT under any circumstances */
71: if ( timer->min_timeout < MIN_TIMEOUT )
72: timer->min_timeout = MIN_TIMEOUT;
73: /* Honor user-specified minimum timeout */
74: if ( timer->timeout < timer->min_timeout )
75: timer->timeout = timer->min_timeout;
76:
77: DBG2 ( "Timer %p started at time %ld (expires at %ld)\n",
78: timer, timer->start, ( timer->start + timer->timeout ) );
79: }
80:
81: /**
82: * Start timer with a specified fixed timeout
83: *
84: * @v timer Retry timer
85: * @v timeout Timeout, in ticks
86: */
87: void start_timer_fixed ( struct retry_timer *timer, unsigned long timeout ) {
88: start_timer ( timer );
89: timer->timeout = timeout;
90: DBG2 ( "Timer %p expiry time changed to %ld\n",
91: timer, ( timer->start + timer->timeout ) );
92: }
93:
94: /**
95: * Stop timer
96: *
97: * @v timer Retry timer
98: *
99: * This stops the timer and updates the timer's timeout value.
100: */
101: void stop_timer ( struct retry_timer *timer ) {
102: unsigned long old_timeout = timer->timeout;
103: unsigned long now = currticks();
104: unsigned long runtime;
105:
106: /* If timer was already stopped, do nothing */
107: if ( ! timer->running )
108: return;
109:
110: list_del ( &timer->list );
111: runtime = ( now - timer->start );
112: timer->running = 0;
113: DBG2 ( "Timer %p stopped at time %ld (ran for %ld)\n",
114: timer, now, runtime );
115:
116: /* Update timer. Variables are:
117: *
118: * r = round-trip time estimate (i.e. runtime)
119: * t = timeout value (i.e. timer->timeout)
120: * s = smoothed round-trip time
121: *
122: * By choice, we set t = 4s, i.e. allow for four times the
123: * normal round-trip time to pass before retransmitting.
124: *
125: * We want to smooth according to s := ( 7 s + r ) / 8
126: *
127: * Since we don't actually store s, this reduces to
128: * t := ( 7 t / 8 ) + ( r / 2 )
129: *
130: */
131: if ( timer->count ) {
132: timer->count--;
133: } else {
134: timer->timeout -= ( timer->timeout >> 3 );
135: timer->timeout += ( runtime >> 1 );
136: if ( timer->timeout != old_timeout ) {
137: DBG ( "Timer %p timeout updated to %ld\n",
138: timer, timer->timeout );
139: }
140: }
141:
142: ref_put ( timer->refcnt );
143: }
144:
145: /**
146: * Handle expired timer
147: *
148: * @v timer Retry timer
149: */
150: static void timer_expired ( struct retry_timer *timer ) {
151: int fail;
152:
153: /* Stop timer without performing RTT calculations */
154: DBG2 ( "Timer %p stopped at time %ld on expiry\n",
155: timer, currticks() );
156: assert ( timer->running );
157: list_del ( &timer->list );
158: timer->running = 0;
159: timer->count++;
160:
161: /* Back off the timeout value */
162: timer->timeout <<= 1;
163: if ( timer->max_timeout == 0 ) /* 0 means "use default timeout" */
164: timer->max_timeout = DEFAULT_MAX_TIMEOUT;
165: if ( ( fail = ( timer->timeout > timer->max_timeout ) ) )
166: timer->timeout = timer->max_timeout;
167: DBG ( "Timer %p timeout backed off to %ld\n",
168: timer, timer->timeout );
169:
170: /* Call expiry callback */
171: timer->expired ( timer, fail );
172:
173: ref_put ( timer->refcnt );
174: }
175:
176: /**
177: * Single-step the retry timer list
178: *
179: * @v process Retry timer process
180: */
181: static void retry_step ( struct process *process __unused ) {
182: struct retry_timer *timer;
183: unsigned long now = currticks();
184: unsigned long used;
185:
186: /* Process at most one timer expiry. We cannot process
187: * multiple expiries in one pass, because one timer expiring
188: * may end up triggering another timer's deletion from the
189: * list.
190: */
191: list_for_each_entry ( timer, &timers, list ) {
192: used = ( now - timer->start );
193: if ( used >= timer->timeout ) {
194: timer_expired ( timer );
195: break;
196: }
197: }
198: }
199:
200: /** Retry timer process */
201: struct process retry_process __permanent_process = {
202: .list = LIST_HEAD_INIT ( retry_process.list ),
203: .step = retry_step,
204: };
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.