openbsd-20021105
[openafs.git] / src / rx / rx_conncache.c
1 /*
2  * Copyright 2000, International Business Machines Corporation and others.
3  * All Rights Reserved.
4  * 
5  * This software has been released under the terms of the IBM Public
6  * License.  For details, see the LICENSE file in the top-level source
7  * directory or online at http://www.openafs.org/dl/license10.html
8  */
9
10 /*
11  * Implement caching of rx connections.
12  */
13
14 #ifdef UKERNEL
15 #include "afs/param.h"
16 #else
17 #include <afs/param.h>
18 #endif
19 #include <afsconfig.h>
20
21 RCSID("$Header$");
22
23 #ifdef UKERNEL
24 #include "afs/sysincludes.h"
25 #include "afsincludes.h"
26 #include "rx/rx.h"
27 #else /* ! UKERNEL */
28 #include <sys/types.h>
29 #include <errno.h>
30 #include <assert.h>
31 #include "rx.h"
32 #endif /* UKERNEL */
33
34 /*
35  * We initialize rxi_connectionCache at compile time, so there is no
36  * need to call queue_Init(&rxi_connectionCache).
37  */
38 static struct rx_queue rxi_connectionCache = { &rxi_connectionCache,
39                                                &rxi_connectionCache };
40
41 #ifdef AFS_PTHREAD_ENV
42 #include <pthread.h>
43 /*
44  * This mutex protects the following global variables:
45  * rxi_connectionCache
46  */
47
48 pthread_mutex_t rxi_connCacheMutex;
49 #define LOCK_CONN_CACHE assert(pthread_mutex_lock(&rxi_connCacheMutex)==0);
50 #define UNLOCK_CONN_CACHE assert(pthread_mutex_unlock(&rxi_connCacheMutex)==0);
51 #else
52 #define LOCK_CONN_CACHE
53 #define UNLOCK_CONN_CACHE
54 #endif /* AFS_PTHREAD_ENV */
55
56 /*
57  * convenience typedef - all the stuff that makes up an rx
58  * connection
59  */
60  
61 typedef struct rx_connParts {
62     unsigned int hostAddr;
63     unsigned short port;
64     unsigned short service;
65     struct rx_securityClass *securityObject;
66     int securityIndex;
67 } rx_connParts_t, *rx_connParts_p;
68  
69 /*
70  * Each element in the cache is represented by the following
71  * structure.  I use an rx_queue to manipulate the cache entries.
72  * inUse tracks the number of calls within this connection that
73  * we know are in use.
74  */
75  
76 typedef struct cache_entry {
77     struct rx_queue queue_header;
78     struct rx_connection *conn;
79     rx_connParts_t parts;
80     int inUse;
81     int hasError;
82 } cache_entry_t, *cache_entry_p;
83
84 /*
85  * Locking hierarchy
86  *
87  *    rxi_connCacheMutex is the only mutex used by these functions.  It should
88  *    be locked when manipulating the connection cache.
89  */
90
91 /*
92  * Internal functions
93  */
94  
95 /*
96  * Compare two connections for equality
97  */
98  
99 static int rxi_CachedConnectionsEqual(rx_connParts_p a, rx_connParts_p b)
100 {
101     return ((a->hostAddr == b->hostAddr) &&
102             (a->port == b->port) &&
103             (a->service == b->service) &&
104             (a->securityObject == b->securityObject) &&
105             (a->securityIndex == b->securityIndex));
106 }
107
108 /*
109  * Check the cache for a connection
110  */
111  
112 static int rxi_FindCachedConnection(rx_connParts_p parts, struct rx_connection **conn)
113 {
114     int error = 0;
115     cache_entry_p cacheConn, nCacheConn;
116  
117     for(queue_Scan(&rxi_connectionCache, cacheConn, nCacheConn, cache_entry)) {
118         if((rxi_CachedConnectionsEqual(parts, &cacheConn->parts)) &&
119            (cacheConn->inUse < RX_MAXCALLS) &&
120            (cacheConn->hasError == 0)) {
121            cacheConn->inUse++;
122            *conn = cacheConn->conn;
123            error = 1;
124            break;
125         }
126     }
127     return error;
128 }
129  
130 /*
131  * Create an rx connection and return it to the caller
132  */
133  
134 /*
135  * Add a connection to the cache keeping track of the input
136  * arguments that were used to create it
137  */
138  
139 static void rxi_AddCachedConnection(rx_connParts_p parts, struct rx_connection **conn)
140 {
141     cache_entry_p new_entry;
142  
143     if((new_entry = (cache_entry_p) malloc(sizeof(cache_entry_t)))) {
144         new_entry->conn = *conn;
145         new_entry->parts = *parts;
146         new_entry->inUse = 1;
147         new_entry->hasError = 0;
148         queue_Prepend(&rxi_connectionCache, new_entry);
149     }
150  
151     /*
152      * if malloc fails, we fail silently
153      */
154
155     return;
156 }
157  
158 /*
159  * Get a connection by first checking to see if any matching
160  * available connections are stored in the cache.
161  * Create a new connection if none are currently available.
162  */
163  
164 static int rxi_GetCachedConnection(rx_connParts_p parts, struct rx_connection **conn)
165 {
166     int error = 0;
167  
168     /*
169      * Look to see if we have a cached connection
170      *
171      * Note - we hold the connection cache mutex for the entire
172      * search/create/enter operation.  We want this entire block to
173      * be atomic so that in the event four threads all pass through
174      * this code at the same time only one actually allocates the
175      * new connection and the other three experience cache hits.
176      *
177      * We intentionally slow down throughput in order to
178      * increase the frequency of cache hits.
179      */
180  
181     LOCK_CONN_CACHE
182     if (!rxi_FindCachedConnection(parts, conn)) {
183         /*
184          * Create a new connection and enter it in the cache
185          */
186         if ((*conn = rx_NewConnection(parts->hostAddr, parts->port,
187                                       parts->service, parts->securityObject,
188                                       parts->securityIndex))) {
189             rxi_AddCachedConnection(parts, conn);
190         }
191         else {
192             error = 1;
193         }
194     }
195     UNLOCK_CONN_CACHE
196     return error;
197 }
198
199 /*
200  * Delete remaining entries in the cache.
201  * Note - only call this routine from rx_Finalize.
202  */
203
204 void rxi_DeleteCachedConnections(void)
205 {
206     cache_entry_p cacheConn, nCacheConn;
207  
208     LOCK_CONN_CACHE
209     for(queue_Scan(&rxi_connectionCache, cacheConn, nCacheConn, cache_entry)) {
210         if(!cacheConn) break;
211         queue_Remove(cacheConn);
212         rxi_DestroyConnection(cacheConn->conn);
213         free(cacheConn);
214     }
215     UNLOCK_CONN_CACHE
216 }
217  
218 /*
219  * External functions
220  */
221  
222 /*
223  * Hand back the caller a connection
224  * The function has the same foot print and return values
225  * as rx_NewConnection.
226  */
227  
228 struct rx_connection *rx_GetCachedConnection(unsigned int remoteAddr,
229         unsigned short port, unsigned short service, struct rx_securityClass *securityObject,
230         int securityIndex)
231 {
232     struct rx_connection *conn = NULL;
233     rx_connParts_t parts;
234  
235     parts.hostAddr = remoteAddr;
236     parts.port = port;
237     parts.service = service;
238     parts.securityObject = securityObject;
239     parts.securityIndex = securityIndex;
240     /*
241      * Get a connection matching the user's request
242      * note we don't propagate the error returned by rxi_GetCachedConnection
243      * since rx_NewConnection doesn't return errors either.
244      */
245     rxi_GetCachedConnection(&parts, &conn);
246  
247     return conn;
248 }
249  
250 /*
251  * Release a connection we previously handed out
252  */
253  
254 void rx_ReleaseCachedConnection(struct rx_connection *conn)
255 {
256     cache_entry_p cacheConn, nCacheConn;
257  
258     LOCK_CONN_CACHE
259     for(queue_Scan(&rxi_connectionCache, cacheConn, nCacheConn, cache_entry)) {
260         if(conn == cacheConn->conn) {
261             cacheConn->inUse--;
262             /*
263              * check to see if the connection is in error.
264              * If it is, mark its cache entry so it won't be 
265              * given out subsequently.  If nobody is using it, delete
266              * it from the cache
267              */
268             if(rx_ConnError(conn)) {
269                 cacheConn->hasError = 1;
270                 if(cacheConn->inUse == 0) {
271                     queue_Remove(cacheConn);
272                     rxi_DestroyConnection(cacheConn->conn);
273                     free(cacheConn);
274                 }
275             }
276             break;
277         }
278     }
279     UNLOCK_CONN_CACHE
280 }