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