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