retry.c

Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>.
00003  *
00004  * This program is free software; you can redistribute it and/or
00005  * modify it under the terms of the GNU General Public License as
00006  * published by the Free Software Foundation; either version 2 of the
00007  * License, or any later version.
00008  *
00009  * This program is distributed in the hope that it will be useful, but
00010  * WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012  * General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU General Public License
00015  * along with this program; if not, write to the Free Software
00016  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
00017  */
00018 
00019 FILE_LICENCE ( GPL2_OR_LATER );
00020 
00021 #include <stddef.h>
00022 #include <gpxe/timer.h>
00023 #include <gpxe/list.h>
00024 #include <gpxe/process.h>
00025 #include <gpxe/init.h>
00026 #include <gpxe/retry.h>
00027 
00028 /** @file
00029  *
00030  * Retry timers
00031  *
00032  * A retry timer is a binary exponential backoff timer.  It can be
00033  * used to build automatic retransmission into network protocols.
00034  *
00035  * This implementation of the timer is designed to satisfy RFC 2988
00036  * and therefore be usable as a TCP retransmission timer.
00037  *
00038  * 
00039  */
00040 
00041 /* The theoretical minimum that the algorithm in stop_timer() can
00042  * adjust the timeout back down to is seven ticks, so set the minimum
00043  * timeout to at least that value for the sake of consistency.
00044  */
00045 #define MIN_TIMEOUT 7
00046 
00047 /** List of running timers */
00048 static LIST_HEAD ( timers );
00049 
00050 /**
00051  * Start timer
00052  *
00053  * @v timer             Retry timer
00054  *
00055  * This starts the timer running with the current timeout value.  If
00056  * stop_timer() is not called before the timer expires, the timer will
00057  * be stopped and the timer's callback function will be called.
00058  */
00059 void start_timer ( struct retry_timer *timer ) {
00060         if ( ! timer->running )
00061                 list_add ( &timer->list, &timers );
00062         timer->start = currticks();
00063         timer->running = 1;
00064 
00065         /* 0 means "use default timeout" */
00066         if ( timer->min_timeout == 0 )
00067                 timer->min_timeout = DEFAULT_MIN_TIMEOUT;
00068         /* We must never be less than MIN_TIMEOUT under any circumstances */
00069         if ( timer->min_timeout < MIN_TIMEOUT )
00070                 timer->min_timeout = MIN_TIMEOUT;
00071         /* Honor user-specified minimum timeout */
00072         if ( timer->timeout < timer->min_timeout )
00073                 timer->timeout = timer->min_timeout;
00074 
00075         DBG2 ( "Timer %p started at time %ld (expires at %ld)\n",
00076                timer, timer->start, ( timer->start + timer->timeout ) );
00077 }
00078 
00079 /**
00080  * Start timer with a specified fixed timeout
00081  *
00082  * @v timer             Retry timer
00083  * @v timeout           Timeout, in ticks
00084  */
00085 void start_timer_fixed ( struct retry_timer *timer, unsigned long timeout ) {
00086         start_timer ( timer );
00087         timer->timeout = timeout;
00088         DBG2 ( "Timer %p expiry time changed to %ld\n",
00089                timer, ( timer->start + timer->timeout ) );
00090 }
00091 
00092 /**
00093  * Stop timer
00094  *
00095  * @v timer             Retry timer
00096  *
00097  * This stops the timer and updates the timer's timeout value.
00098  */
00099 void stop_timer ( struct retry_timer *timer ) {
00100         unsigned long old_timeout = timer->timeout;
00101         unsigned long now = currticks();
00102         unsigned long runtime;
00103 
00104         /* If timer was already stopped, do nothing */
00105         if ( ! timer->running )
00106                 return;
00107 
00108         list_del ( &timer->list );
00109         runtime = ( now - timer->start );
00110         timer->running = 0;
00111         DBG2 ( "Timer %p stopped at time %ld (ran for %ld)\n",
00112                timer, now, runtime );
00113 
00114         /* Update timer.  Variables are:
00115          *
00116          *   r = round-trip time estimate (i.e. runtime)
00117          *   t = timeout value (i.e. timer->timeout)
00118          *   s = smoothed round-trip time
00119          *
00120          * By choice, we set t = 4s, i.e. allow for four times the
00121          * normal round-trip time to pass before retransmitting.
00122          *
00123          * We want to smooth according to s := ( 7 s + r ) / 8
00124          *
00125          * Since we don't actually store s, this reduces to
00126          * t := ( 7 t / 8 ) + ( r / 2 )
00127          *
00128          */
00129         if ( timer->count ) {
00130                 timer->count--;
00131         } else {
00132                 timer->timeout -= ( timer->timeout >> 3 );
00133                 timer->timeout += ( runtime >> 1 );
00134                 if ( timer->timeout != old_timeout ) {
00135                         DBG ( "Timer %p timeout updated to %ld\n",
00136                               timer, timer->timeout );
00137                 }
00138         }
00139 }
00140 
00141 /**
00142  * Handle expired timer
00143  *
00144  * @v timer             Retry timer
00145  */
00146 static void timer_expired ( struct retry_timer *timer ) {
00147         int fail;
00148 
00149         /* Stop timer without performing RTT calculations */
00150         DBG2 ( "Timer %p stopped at time %ld on expiry\n",
00151                timer, currticks() );
00152         assert ( timer->running );
00153         list_del ( &timer->list );
00154         timer->running = 0;
00155         timer->count++;
00156 
00157         /* Back off the timeout value */
00158         timer->timeout <<= 1;
00159         if ( timer->max_timeout == 0 ) /* 0 means "use default timeout" */
00160                 timer->max_timeout = DEFAULT_MAX_TIMEOUT;
00161         if ( ( fail = ( timer->timeout > timer->max_timeout ) ) )
00162                 timer->timeout = timer->max_timeout;
00163         DBG ( "Timer %p timeout backed off to %ld\n",
00164               timer, timer->timeout );
00165 
00166         /* Call expiry callback */
00167         timer->expired ( timer, fail ); 
00168 }
00169 
00170 /**
00171  * Single-step the retry timer list
00172  *
00173  * @v process           Retry timer process
00174  */
00175 static void retry_step ( struct process *process __unused ) {
00176         struct retry_timer *timer;
00177         struct retry_timer *tmp;
00178         unsigned long now = currticks();
00179         unsigned long used;
00180 
00181         list_for_each_entry_safe ( timer, tmp, &timers, list ) {
00182                 used = ( now - timer->start );
00183                 if ( used >= timer->timeout )
00184                         timer_expired ( timer );
00185         }
00186 }
00187 
00188 /** Retry timer process */
00189 struct process retry_process __permanent_process = {
00190         .list = LIST_HEAD_INIT ( retry_process.list ),
00191         .step = retry_step,
00192 };

Generated on Tue Apr 6 20:01:10 2010 for gPXE by  doxygen 1.5.7.1