Update from db-2.3.12.
[kopensolaris-gnu/glibc.git] / db2 / mp / mp_region.c
1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 1996, 1997
5  *      Sleepycat Software.  All rights reserved.
6  */
7 #include "config.h"
8
9 #ifndef lint
10 static const char sccsid[] = "@(#)mp_region.c   10.16 (Sleepycat) 10/25/97";
11 #endif /* not lint */
12
13 #ifndef NO_SYSTEM_INCLUDES
14 #include <sys/types.h>
15 #include <sys/stat.h>
16
17 #include <errno.h>
18 #include <fcntl.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <unistd.h>
22 #endif
23
24 #include "db_int.h"
25 #include "shqueue.h"
26 #include "db_shash.h"
27 #include "mp.h"
28 #include "common_ext.h"
29
30 /*
31  * __memp_ralloc --
32  *      Allocate some space in the mpool region.
33  *
34  * PUBLIC: int __memp_ralloc __P((DB_MPOOL *, size_t, size_t *, void *));
35  */
36 int
37 __memp_ralloc(dbmp, len, offsetp, retp)
38         DB_MPOOL *dbmp;
39         size_t len, *offsetp;
40         void *retp;
41 {
42         BH *bhp, *nbhp;
43         MPOOL *mp;
44         MPOOLFILE *mfp;
45         size_t fsize, total;
46         int nomore, restart, ret, wrote;
47         void *p;
48
49         mp = dbmp->mp;
50
51         nomore = 0;
52 alloc:  if ((ret = __db_shalloc(dbmp->addr, len, MUTEX_ALIGNMENT, &p)) == 0) {
53                 if (offsetp != NULL)
54                         *offsetp = R_OFFSET(dbmp, p);
55                 *(void **)retp = p;
56                 return (0);
57         }
58         if (nomore) {
59                 __db_err(dbmp->dbenv, "%s", strerror(ret));
60                 return (ret);
61         }
62
63         /* Look for a buffer on the free list that's the right size. */
64         for (bhp =
65             SH_TAILQ_FIRST(&mp->bhfq, __bh); bhp != NULL; bhp = nbhp) {
66                 nbhp = SH_TAILQ_NEXT(bhp, q, __bh);
67
68                 if (__db_shsizeof(bhp) == len) {
69                         SH_TAILQ_REMOVE(&mp->bhfq, bhp, q, __bh);
70                         if (offsetp != NULL)
71                                 *offsetp = R_OFFSET(dbmp, bhp);
72                         *(void **)retp = bhp;
73                         return (0);
74                 }
75         }
76
77         /* Discard from the free list until we've freed enough memory. */
78         total = 0;
79         for (bhp =
80             SH_TAILQ_FIRST(&mp->bhfq, __bh); bhp != NULL; bhp = nbhp) {
81                 nbhp = SH_TAILQ_NEXT(bhp, q, __bh);
82
83                 SH_TAILQ_REMOVE(&mp->bhfq, bhp, q, __bh);
84                 __db_shalloc_free(dbmp->addr, bhp);
85                 --mp->stat.st_page_clean;
86
87                 /*
88                  * Retry as soon as we've freed up sufficient space.  If we
89                  * have to coalesce of memory to satisfy the request, don't
90                  * try until it's likely (possible?) that we'll succeed.
91                  */
92                 total += fsize = __db_shsizeof(bhp);
93                 if (fsize >= len || total >= 3 * len)
94                         goto alloc;
95         }
96
97 retry:  /* Find a buffer we can flush; pure LRU. */
98         total = 0;
99         for (bhp =
100             SH_TAILQ_FIRST(&mp->bhq, __bh); bhp != NULL; bhp = nbhp) {
101                 nbhp = SH_TAILQ_NEXT(bhp, q, __bh);
102
103                 /* Ignore pinned or locked (I/O in progress) buffers. */
104                 if (bhp->ref != 0 || F_ISSET(bhp, BH_LOCKED))
105                         continue;
106
107                 /* Find the associated MPOOLFILE. */
108                 mfp = R_ADDR(dbmp, bhp->mf_offset);
109
110                 /*
111                  * Write the page if it's dirty.
112                  *
113                  * If we wrote the page, fall through and free the buffer.  We
114                  * don't have to rewalk the list to acquire the buffer because
115                  * it was never available for any other process to modify it.
116                  * If we didn't write the page, but we discarded and reacquired
117                  * the region lock, restart the buffer list walk.  If we neither
118                  * wrote the buffer nor discarded the region lock, continue down
119                  * the buffer list.
120                  */
121                 if (F_ISSET(bhp, BH_DIRTY)) {
122                         if ((ret = __memp_bhwrite(dbmp,
123                             mfp, bhp, &restart, &wrote)) != 0)
124                                 return (ret);
125
126                         /*
127                          * It's possible that another process wants this buffer
128                          * and incremented the ref count while we were writing
129                          * it.
130                          */
131                         if (bhp->ref != 0)
132                                 goto retry;
133
134                         if (wrote)
135                                 ++mp->stat.st_rw_evict;
136                         else {
137                                 if (restart)
138                                         goto retry;
139                                 continue;
140                         }
141                 } else
142                         ++mp->stat.st_ro_evict;
143
144                 /*
145                  * Check to see if the buffer is the size we're looking for.
146                  * If it is, simply reuse it.
147                  */
148                 total += fsize = __db_shsizeof(bhp);
149                 if (fsize == len) {
150                         __memp_bhfree(dbmp, mfp, bhp, 0);
151
152                         if (offsetp != NULL)
153                                 *offsetp = R_OFFSET(dbmp, bhp);
154                         *(void **)retp = bhp;
155                         return (0);
156                 }
157
158                 /* Free the buffer. */
159                 __memp_bhfree(dbmp, mfp, bhp, 1);
160
161                 /*
162                  * Retry as soon as we've freed up sufficient space.  If we
163                  * have to coalesce of memory to satisfy the request, don't
164                  * try until it's likely (possible?) that we'll succeed.
165                  */
166                 if (fsize >= len || total >= 3 * len)
167                         goto alloc;
168
169                 /* Restart the walk if we discarded the region lock. */
170                 if (restart)
171                         goto retry;
172         }
173         nomore = 1;
174         goto alloc;
175 }
176
177 /*
178  * __memp_ropen --
179  *      Attach to, and optionally create, the mpool region.
180  *
181  * PUBLIC: int __memp_ropen
182  * PUBLIC:    __P((DB_MPOOL *, const char *, size_t, int, int));
183  */
184 int
185 __memp_ropen(dbmp, path, cachesize, mode, flags)
186         DB_MPOOL *dbmp;
187         const char *path;
188         size_t cachesize;
189         int mode, flags;
190 {
191         MPOOL *mp;
192         size_t rlen;
193         int fd, newregion, ret, retry_cnt;
194
195         /*
196          * Unlike other DB subsystems, mpool can't simply grow the region
197          * because it returns pointers into the region to its clients.  To
198          * "grow" the region, we'd have to allocate a new region and then
199          * store a region number in the structures that reference regional
200          * objects.  It's reasonable that we fail regardless, as clients
201          * shouldn't have every page in the region pinned, so the only
202          * "failure" mode should be a performance penalty because we don't
203          * find a page in the cache that we'd like to have found.
204          *
205          * Up the user's cachesize by 25% to account for our overhead.
206          */
207         if (cachesize < DB_CACHESIZE_MIN)
208                 if (cachesize == 0)
209                         cachesize = DB_CACHESIZE_DEF;
210                 else
211                         cachesize = DB_CACHESIZE_MIN;
212         rlen = cachesize + cachesize / 4;
213
214         /* Map in the region. */
215         retry_cnt = newregion = 0;
216 retry:  if (LF_ISSET(DB_CREATE)) {
217                 /*
218                  * If it's a private mpool, use malloc, it's a lot faster than
219                  * instantiating a region.
220                  *
221                  * XXX
222                  * If we're doing locking and don't have spinlocks for this
223                  * architecture, we'd have to instantiate the file, we need
224                  * the file descriptor for locking.  However, it should not
225                  * be possible for DB_THREAD to be set if HAVE_SPINLOCKS aren't
226                  * defined.
227                  */
228                 if (F_ISSET(dbmp, MP_ISPRIVATE)) {
229                         if ((dbmp->maddr = __db_malloc(rlen)) == NULL)
230                                 ret = ENOMEM;
231                         else
232                                 ret = __db_rinit(dbmp->dbenv,
233                                     dbmp->maddr, 0, rlen, 0);
234                 } else
235                         ret = __db_rcreate(dbmp->dbenv, DB_APP_NONE, path,
236                             DB_DEFAULT_MPOOL_FILE, mode, rlen, &fd,
237                             &dbmp->maddr);
238                 if (ret == 0) {
239                         /* Put the MPOOL structure first in the region. */
240                         mp = dbmp->maddr;
241
242                         SH_TAILQ_INIT(&mp->bhq);
243                         SH_TAILQ_INIT(&mp->bhfq);
244                         SH_TAILQ_INIT(&mp->mpfq);
245
246                         /* Initialize the rest of the region as free space. */
247                         dbmp->addr = (u_int8_t *)dbmp->maddr + sizeof(MPOOL);
248                         __db_shalloc_init(dbmp->addr, rlen - sizeof(MPOOL));
249
250                         /*
251                          *
252                          * Pretend that the cache will be broken up into 4K
253                          * pages, and that we want to keep it under, say, 10
254                          * pages on each chain.  This means a 256MB cache will
255                          * allocate ~6500 offset pairs.
256                          */
257                         mp->htab_buckets =
258                             __db_tablesize((cachesize / (4 * 1024)) / 10);
259
260                         /* Allocate hash table space and initialize it. */
261                         if ((ret = __db_shalloc(dbmp->addr,
262                             mp->htab_buckets * sizeof(DB_HASHTAB),
263                             0, &dbmp->htab)) != 0)
264                                 goto err;
265                         __db_hashinit(dbmp->htab, mp->htab_buckets);
266                         mp->htab = R_OFFSET(dbmp, dbmp->htab);
267
268                         ZERO_LSN(mp->lsn);
269                         mp->lsn_cnt = 0;
270
271                         memset(&mp->stat, 0, sizeof(mp->stat));
272                         mp->stat.st_cachesize = cachesize;
273
274                         mp->flags = 0;
275
276                         newregion = 1;
277                 } else if (ret != EEXIST)
278                         return (ret);
279         }
280
281         /* If we didn't or couldn't create the region, try and join it. */
282         if (!newregion &&
283             (ret = __db_ropen(dbmp->dbenv, DB_APP_NONE,
284             path, DB_DEFAULT_MPOOL_FILE, 0, &fd, &dbmp->maddr)) != 0) {
285                 /*
286                  * If we failed because the file wasn't available, wait a
287                  * second and try again.
288                  */
289                 if (ret == EAGAIN && ++retry_cnt < 3) {
290                         (void)__db_sleep(1, 0);
291                         goto retry;
292                 }
293                 return (ret);
294         }
295
296         /* Set up the common pointers. */
297         dbmp->mp = dbmp->maddr;
298         dbmp->addr = (u_int8_t *)dbmp->maddr + sizeof(MPOOL);
299
300         /*
301          * If not already locked, lock the region -- if it's a new region,
302          * then either __db_rcreate() locked it for us or we malloc'd it
303          * instead of creating a region, neither of which requires locking
304          * here.
305          */
306         if (!newregion)
307                 LOCKREGION(dbmp);
308
309         /*
310          * Get the hash table address; it's on the shared page, so we have
311          * to lock first.
312          */
313         dbmp->htab = R_ADDR(dbmp, dbmp->mp->htab);
314
315         dbmp->fd = fd;
316
317         /* If we locked the region, release it now. */
318         if (!F_ISSET(dbmp, MP_ISPRIVATE))
319                 UNLOCKREGION(dbmp);
320         return (0);
321
322 err:    if (fd != -1) {
323                 dbmp->fd = fd;
324                 (void)__memp_rclose(dbmp);
325         }
326
327         if (newregion)
328                 (void)memp_unlink(path, 1, dbmp->dbenv);
329         return (ret);
330 }
331
332 /*
333  * __memp_rclose --
334  *      Close the mpool region.
335  *
336  * PUBLIC: int __memp_rclose __P((DB_MPOOL *));
337  */
338 int
339 __memp_rclose(dbmp)
340         DB_MPOOL *dbmp;
341 {
342         if (F_ISSET(dbmp, MP_ISPRIVATE)) {
343                 __db_free(dbmp->maddr);
344                 return (0);
345         }
346         return (__db_rclose(dbmp->dbenv, dbmp->fd, dbmp->maddr));
347 }