1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "primpl.h"
7 
8 #include <string.h>
9 
10 /*****************************************************************************/
11 /*****************************************************************************/
12 /************************** File descriptor caching **************************/
13 /*****************************************************************************/
14 /*****************************************************************************/
15 
16 /*
17 ** This code is built into debuggable versions of NSPR to assist in
18 ** finding misused file descriptors. Since file descritors (PRFileDesc)
19 ** are identified by a pointer to their structure, they can be the
20 ** target of dangling references. Furthermore, NSPR caches and tries
21 ** to aggressively reuse file descriptors, leading to more ambiguity.
22 ** The following code will allow a debugging client to set environment
23 ** variables and control the number of file descriptors that will be
24 ** preserved before they are recycled. The environment variables are
25 ** NSPR_FD_CACHE_SIZE_LOW and NSPR_FD_CACHE_SIZE_HIGH. The former sets
26 ** the number of descriptors NSPR will allocate before beginning to
27 ** recycle. The latter is the maximum number permitted in the cache
28 ** (exclusive of those in use) at a time.
29 */
30 typedef struct _PR_Fd_Cache
31 {
32     PRLock *ml;
33     PRIntn count;
34     PRFileDesc *head, *tail;
35     PRIntn limit_low, limit_high;
36 } _PR_Fd_Cache;
37 
38 static _PR_Fd_Cache _pr_fd_cache;
39 
40 
41 /*
42 ** Get a FileDescriptor from the cache if one exists. If not allocate
43 ** a new one from the heap.
44 */
_PR_Getfd(void)45 PRFileDesc *_PR_Getfd(void)
46 {
47     PRFileDesc *fd;
48     /*
49     ** $$$
50     ** This may look a little wasteful. We'll see. Right now I want to
51     ** be able to toggle between caching and not at runtime to measure
52     ** the differences. If it isn't too annoying, I'll leave it in.
53     ** $$$$
54     **
55     ** The test is against _pr_fd_cache.limit_high. If that's zero,
56     ** we're not doing the extended cache but going for performance.
57     */
58     if (0 == _pr_fd_cache.limit_high)
59     {
60         goto allocate;
61     }
62     else
63     {
64         do
65         {
66             if (NULL == _pr_fd_cache.head) goto allocate;  /* nothing there */
67             if (_pr_fd_cache.count < _pr_fd_cache.limit_low) goto allocate;
68 
69             /* we "should" be able to extract an fd from the cache */
70             PR_Lock(_pr_fd_cache.ml);  /* need the lock to do this safely */
71             fd = _pr_fd_cache.head;  /* protected extraction */
72             if (NULL == fd)  /* unexpected, but not fatal */
73             {
74                 PR_ASSERT(0 == _pr_fd_cache.count);
75                 PR_ASSERT(NULL == _pr_fd_cache.tail);
76             }
77             else
78             {
79                 _pr_fd_cache.count -= 1;
80                 _pr_fd_cache.head = fd->higher;
81                 if (NULL == _pr_fd_cache.head)
82                 {
83                     PR_ASSERT(0 == _pr_fd_cache.count);
84                     _pr_fd_cache.tail = NULL;
85                 }
86                 PR_ASSERT(&_pr_faulty_methods == fd->methods);
87                 PR_ASSERT(PR_INVALID_IO_LAYER == fd->identity);
88                 PR_ASSERT(_PR_FILEDESC_FREED == fd->secret->state);
89             }
90             PR_Unlock(_pr_fd_cache.ml);
91 
92         } while (NULL == fd);  /* then go around and allocate a new one */
93     }
94 
95 finished:
96     fd->dtor = NULL;
97     fd->lower = fd->higher = NULL;
98     fd->identity = PR_NSPR_IO_LAYER;
99     memset(fd->secret, 0, sizeof(PRFilePrivate));
100     return fd;
101 
102 allocate:
103     fd = PR_NEW(PRFileDesc);
104     if (NULL != fd)
105     {
106         fd->secret = PR_NEW(PRFilePrivate);
107         if (NULL == fd->secret) PR_DELETE(fd);
108     }
109     if (NULL != fd) goto finished;
110     else return NULL;
111 
112 }  /* _PR_Getfd */
113 
114 /*
115 ** Return a file descriptor to the cache unless there are too many in
116 ** there already. If put in cache, clear the fields first.
117 */
_PR_Putfd(PRFileDesc * fd)118 void _PR_Putfd(PRFileDesc *fd)
119 {
120     PR_ASSERT(PR_NSPR_IO_LAYER == fd->identity);
121     fd->methods = &_pr_faulty_methods;
122     fd->identity = PR_INVALID_IO_LAYER;
123     fd->secret->state = _PR_FILEDESC_FREED;
124 
125     if (0 != _pr_fd_cache.limit_high)
126     {
127         if (_pr_fd_cache.count < _pr_fd_cache.limit_high)
128         {
129             PR_Lock(_pr_fd_cache.ml);
130             if (NULL == _pr_fd_cache.tail)
131             {
132                 PR_ASSERT(0 == _pr_fd_cache.count);
133                 PR_ASSERT(NULL == _pr_fd_cache.head);
134                 _pr_fd_cache.head = _pr_fd_cache.tail = fd;
135             }
136             else
137             {
138                 PR_ASSERT(NULL == _pr_fd_cache.tail->higher);
139                 _pr_fd_cache.tail->higher = fd;
140                 _pr_fd_cache.tail = fd;  /* new value */
141             }
142             fd->higher = NULL;  /* always so */
143             _pr_fd_cache.count += 1;  /* count the new entry */
144             PR_Unlock(_pr_fd_cache.ml);
145             return;
146         }
147     }
148 
149     PR_Free(fd->secret);
150     PR_Free(fd);
151 }  /* _PR_Putfd */
152 
PR_SetFDCacheSize(PRIntn low,PRIntn high)153 PR_IMPLEMENT(PRStatus) PR_SetFDCacheSize(PRIntn low, PRIntn high)
154 {
155     /*
156     ** This can be called at any time, may adjust the cache sizes,
157     ** turn the caches off, or turn them on. It is not dependent
158     ** on the compilation setting of DEBUG.
159     */
160     if (!_pr_initialized) _PR_ImplicitInitialization();
161 
162     if (low > high) low = high;  /* sanity check the params */
163 
164     PR_Lock(_pr_fd_cache.ml);
165     _pr_fd_cache.limit_high = high;
166     _pr_fd_cache.limit_low = low;
167     PR_Unlock(_pr_fd_cache.ml);
168     return PR_SUCCESS;
169 }  /* PR_SetFDCacheSize */
170 
_PR_InitFdCache(void)171 void _PR_InitFdCache(void)
172 {
173     /*
174     ** The fd caching is enabled by default for DEBUG builds,
175     ** disabled by default for OPT builds. That default can
176     ** be overridden at runtime using environment variables
177     ** or a super-wiz-bang API.
178     */
179     const char *low = PR_GetEnv("NSPR_FD_CACHE_SIZE_LOW");
180     const char *high = PR_GetEnv("NSPR_FD_CACHE_SIZE_HIGH");
181 
182     /*
183     ** _low is allowed to be zero, _high is not.
184     ** If _high is zero, we're not doing the caching.
185     */
186 
187     _pr_fd_cache.limit_low = 0;
188 #if defined(DEBUG)
189     _pr_fd_cache.limit_high = FD_SETSIZE;
190 #else
191     _pr_fd_cache.limit_high = 0;
192 #endif  /* defined(DEBUG) */
193 
194     if (NULL != low) _pr_fd_cache.limit_low = atoi(low);
195     if (NULL != high) _pr_fd_cache.limit_high = atoi(high);
196 
197     if (_pr_fd_cache.limit_low < 0)
198         _pr_fd_cache.limit_low = 0;
199     if (_pr_fd_cache.limit_low > FD_SETSIZE)
200         _pr_fd_cache.limit_low = FD_SETSIZE;
201 
202     if (_pr_fd_cache.limit_high > FD_SETSIZE)
203         _pr_fd_cache.limit_high = FD_SETSIZE;
204 
205     if (_pr_fd_cache.limit_high < _pr_fd_cache.limit_low)
206         _pr_fd_cache.limit_high = _pr_fd_cache.limit_low;
207 
208     _pr_fd_cache.ml = PR_NewLock();
209     PR_ASSERT(NULL != _pr_fd_cache.ml);
210 
211 }  /* _PR_InitFdCache */
212 
_PR_CleanupFdCache(void)213 void _PR_CleanupFdCache(void)
214 {
215     PRFileDesc *fd, *next;
216 
217     for (fd = _pr_fd_cache.head; fd != NULL; fd = next)
218     {
219         next = fd->higher;
220         PR_DELETE(fd->secret);
221         PR_DELETE(fd);
222     }
223     _pr_fd_cache.head = NULL;
224     _pr_fd_cache.tail = NULL;
225     _pr_fd_cache.count = 0;
226     PR_DestroyLock(_pr_fd_cache.ml);
227     _pr_fd_cache.ml = NULL;
228 }  /* _PR_CleanupFdCache */
229 
230 /* prfdcach.c */
231