mp_fget.c

Go to the documentation of this file.
00001 /*-
00002  * See the file LICENSE for redistribution information.
00003  *
00004  * Copyright (c) 1996, 1997, 1998, 1999, 2000
00005  *      Sleepycat Software.  All rights reserved.
00006  */
00007 #include "config.h"
00008 
00009 #ifndef lint
00010 static const char revid[] = "$Id: mp__fget_8c-source.html,v 1.1 2008/06/08 10:20:45 sebdiaz Exp $";
00011 #endif /* not lint */
00012 
00013 #ifndef NO_SYSTEM_INCLUDES
00014 #include <sys/types.h>
00015 
00016 #include <errno.h>
00017 #include <string.h>
00018 #endif
00019 
00020 #ifdef  HAVE_RPC
00021 #include "db_server.h"
00022 #endif
00023 
00024 #include "db_int.h"
00025 #include "db_shash.h"
00026 #include "db_page.h"
00027 #include "mp.h"
00028 
00029 #ifdef DEBUG
00030 #include "WordMonitor.h"
00031 #endif /* DEBUG */
00032 
00033 #ifdef HAVE_RPC
00034 #include "gen_client_ext.h"
00035 #include "rpc_client_ext.h"
00036 #endif
00037 
00038 /*
00039  * CDB_memp_fget --
00040  *      Get a page from the file.
00041  */
00042 int
00043 CDB_memp_fget(dbmfp, pgnoaddr, flags, addrp)
00044         DB_MPOOLFILE *dbmfp;
00045         db_pgno_t *pgnoaddr;
00046         u_int32_t flags;
00047         void *addrp;
00048 {
00049         BH *bhp;
00050         DB_ENV *dbenv;
00051         DB_MPOOL *dbmp;
00052         DB_HASHTAB *dbht;
00053         MPOOL *c_mp, *mp;
00054         MPOOLFILE *mfp;
00055         size_t n_bucket, n_cache, mf_offset;
00056         u_int32_t st_hsearch;
00057         int b_incr, first, ret;
00058 
00059         dbmp = dbmfp->dbmp;
00060         dbenv = dbmp->dbenv;
00061         mp = dbmp->reginfo[0].primary;
00062         mfp = dbmfp->mfp;
00063 #ifdef HAVE_RPC
00064         if (F_ISSET(dbenv, DB_ENV_RPCCLIENT))
00065                 return (__dbcl_memp_fget(dbmfp, pgnoaddr, flags, addrp));
00066 #endif
00067 
00068         PANIC_CHECK(dbenv);
00069 
00070         /*
00071          * Validate arguments.
00072          *
00073          * !!!
00074          * Don't test for DB_MPOOL_CREATE and DB_MPOOL_NEW flags for readonly
00075          * files here, and create non-existent pages in readonly files if the
00076          * flags are set, later.  The reason is that the hash access method
00077          * wants to get empty pages that don't really exist in readonly files.
00078          * The only alternative is for hash to write the last "bucket" all the
00079          * time, which we don't want to do because one of our big goals in life
00080          * is to keep database files small.  It's sleazy as hell, but we catch
00081          * any attempt to actually write the file in CDB_memp_fput().
00082          */
00083 #define OKFLAGS \
00084     (DB_MPOOL_CREATE | DB_MPOOL_LAST | DB_MPOOL_NEW | DB_MPOOL_NEW_GROUP)
00085         if (flags != 0) {
00086                 if ((ret = CDB___db_fchk(dbenv, "CDB_memp_fget", flags, OKFLAGS)) != 0)
00087                         return (ret);
00088 
00089                 switch (flags) {
00090                 case DB_MPOOL_CREATE:
00091                 case DB_MPOOL_LAST:
00092                 case DB_MPOOL_NEW:
00093                 case DB_MPOOL_NEW_GROUP:
00094                 case 0:
00095                         break;
00096                 default:
00097                         return (CDB___db_ferr(dbenv, "CDB_memp_fget", 1));
00098                 }
00099         }
00100 
00101 #ifdef DIAGNOSTIC
00102         /*
00103          * XXX
00104          * We want to switch threads as often as possible.  Yield every time
00105          * we get a new page to ensure contention.
00106          */
00107         if (DB_GLOBAL(db_pageyield))
00108                 CDB___os_yield(dbenv, 1);
00109 #endif
00110 
00111         /* Initialize remaining local variables. */
00112         mf_offset = R_OFFSET(dbmp->reginfo, mfp);
00113         bhp = NULL;
00114         st_hsearch = 0;
00115         b_incr = ret = 0;
00116 
00117         R_LOCK(dbenv, dbmp->reginfo);
00118 
00119         /*
00120          * Check for the new, last or last + 1 page requests.
00121          *
00122          * Examine and update the file's last_pgno value.  We don't care if
00123          * the last_pgno value immediately changes due to another thread --
00124          * at this instant in time, the value is correct.  We do increment the
00125          * current last_pgno value if the thread is asking for a new page,
00126          * however, to ensure that two threads creating pages don't get the
00127          * same one.
00128          *
00129          * If we create a page, there is the potential that a page after it
00130          * in the file will be written before it will be written.  Recovery
00131          * depends on pages that are "created" in the file by subsequent pages
00132          * being written be zeroed out, not have random garbage.  Ensure that
00133          * the OS agrees.
00134          *
00135          * !!!
00136          * DB_MPOOL_NEW_GROUP is undocumented -- the hash access method needs
00137          * to allocate contiguous groups of pages in order to do subdatabases.
00138          * We return the first page in the group, but the caller must put an
00139          * LSN on the *last* page and write it, otherwise after a crash we may
00140          * not create all of the pages we need to create.
00141          */
00142         if (LF_ISSET(DB_MPOOL_LAST | DB_MPOOL_NEW | DB_MPOOL_NEW_GROUP)) {
00143                 if (LF_ISSET(DB_MPOOL_NEW)) {
00144                         if ((ret = CDB___os_fpinit(dbenv,
00145                             &dbmfp->fh, mfp->last_pgno + 1,
00146                             1, mfp->stat.st_pagesize)) != 0) {
00147                                 R_UNLOCK(dbenv, dbmp->reginfo);
00148                                 return (ret);
00149                         }
00150                         ++mfp->last_pgno;
00151 #ifdef DEBUG
00152                         word_monitor_set(DB_MONITOR(dbenv), WORD_MONITOR_PGNO, mfp->last_pgno);
00153 #endif /* DEBUG */
00154                 }
00155                 if (LF_ISSET(DB_MPOOL_NEW_GROUP)) {
00156                         if ((ret = CDB___os_fpinit(dbenv,
00157                             &dbmfp->fh, mfp->last_pgno + 1,
00158                             (int)*pgnoaddr, mfp->stat.st_pagesize)) != 0) {
00159                                 R_UNLOCK(dbenv, dbmp->reginfo);
00160                                 return (ret);
00161                         }
00162                         mfp->last_pgno += *pgnoaddr;
00163                 }
00164                 *pgnoaddr = mfp->last_pgno;
00165         }
00166 
00167         /*
00168          * Determine the hash bucket where this page will live, and get local
00169          * pointers to the cache and its hash table.
00170          */
00171         n_cache = NCACHE(mp, *pgnoaddr);
00172         c_mp = dbmp->reginfo[n_cache].primary;
00173         n_bucket = NBUCKET(c_mp, mf_offset, *pgnoaddr);
00174         dbht = R_ADDR(&dbmp->reginfo[n_cache], c_mp->htab);
00175 
00176         if (LF_ISSET(DB_MPOOL_NEW | DB_MPOOL_NEW_GROUP))
00177                 goto alloc;
00178 
00179         /*
00180          * If mmap'ing the file and the page is not past the end of the file,
00181          * just return a pointer.
00182          *
00183          * The page may be past the end of the file, so check the page number
00184          * argument against the original length of the file.  If we previously
00185          * returned pages past the original end of the file, last_pgno will
00186          * have been updated to match the "new" end of the file, and checking
00187          * against it would return pointers past the end of the mmap'd region.
00188          *
00189          * If another process has opened the file for writing since we mmap'd
00190          * it, we will start playing the game by their rules, i.e. everything
00191          * goes through the cache.  All pages previously returned will be safe,
00192          * as long as the correct locking protocol was observed.
00193          *
00194          * XXX
00195          * We don't discard the map because we don't know when all of the
00196          * pages will have been discarded from the process' address space.
00197          * It would be possible to do so by reference counting the open
00198          * pages from the mmap, but it's unclear to me that it's worth it.
00199          */
00200         if (dbmfp->addr != NULL && F_ISSET(mfp, MP_CAN_MMAP)) {
00201                 if (*pgnoaddr > mfp->orig_last_pgno) {
00202                         /*
00203                          * !!!
00204                          * See the comment above about non-existent pages and
00205                          * the hash access method.
00206                          */
00207                         if (!LF_ISSET(DB_MPOOL_CREATE)) {
00208                                 CDB___db_err(dbenv, "%s: page %lu doesn't exist",
00209                                     CDB___memp_fn(dbmfp), (u_long)*pgnoaddr);
00210                                 ret = EINVAL;
00211                                 goto err;
00212                         }
00213                 } else {
00214                         *(void **)addrp =
00215                             R_ADDR(dbmfp, *pgnoaddr * mfp->stat.st_pagesize);
00216                         ++mfp->stat.st_map;
00217                         goto done;
00218                 }
00219         }
00220 
00221         /* Search the hash chain for the page. */
00222         for (bhp = SH_TAILQ_FIRST(&dbht[n_bucket], __bh);
00223             bhp != NULL; bhp = SH_TAILQ_NEXT(bhp, hq, __bh)) {
00224                 ++st_hsearch;
00225                 if (bhp->pgno != *pgnoaddr || bhp->mf_offset != mf_offset)
00226                         continue;
00227 
00228                 /* Increment the reference count. */
00229                 if (bhp->ref == UINT16_T_MAX) {
00230                         CDB___db_err(dbenv,
00231                             "%s: page %lu: reference count overflow",
00232                             CDB___memp_fn(dbmfp), (u_long)bhp->pgno);
00233                         ret = EINVAL;
00234                         goto err;
00235                 }
00236 
00237                 /*
00238                  * Increment the reference count.  We may discard the region
00239                  * lock as we evaluate and/or read the buffer, so we need to
00240                  * ensure that it doesn't move and that its contents remain
00241                  * unchanged.
00242                  */
00243                 ++bhp->ref;
00244                 b_incr = 1;
00245 
00246                 /*
00247                  * Any buffer we find might be trouble.
00248                  *
00249                  * BH_LOCKED --
00250                  * I/O is in progress.  Because we've incremented the buffer
00251                  * reference count, we know the buffer can't move.  Unlock
00252                  * the region lock, wait for the I/O to complete, and reacquire
00253                  * the region.
00254                  */
00255                 for (first = 1; F_ISSET(bhp, BH_LOCKED); first = 0) {
00256                         R_UNLOCK(dbenv, dbmp->reginfo);
00257 
00258                         /*
00259                          * Explicitly yield the processor if it's not the first
00260                          * pass through this loop -- if we don't, we might end
00261                          * up running to the end of our CPU quantum as we will
00262                          * simply be swapping between the two locks.
00263                          */
00264                         if (!first)
00265                                 CDB___os_yield(dbenv, 1);
00266 
00267                         MUTEX_LOCK(&bhp->mutex, dbenv->lockfhp);
00268                         /* Wait for I/O to finish... */
00269                         MUTEX_UNLOCK(&bhp->mutex);
00270                         R_LOCK(dbenv, dbmp->reginfo);
00271                 }
00272 
00273                 /*
00274                  * BH_TRASH --
00275                  * The contents of the buffer are garbage.  Shouldn't happen,
00276                  * and this read is likely to fail, but might as well try.
00277                  */
00278                 if (F_ISSET(bhp, BH_TRASH))
00279                         goto reread;
00280 
00281                 /*
00282                  * BH_CALLPGIN --
00283                  * The buffer was converted so it could be written, and the
00284                  * contents need to be converted again.
00285                  */
00286                 if (F_ISSET(bhp, BH_CALLPGIN)) {
00287                         if ((ret = CDB___memp_pg(dbmfp, bhp, 1)) != 0)
00288                                 goto err;
00289                         F_CLR(bhp, BH_CALLPGIN);
00290                 }
00291 
00292                 ++mfp->stat.st_cache_hit;
00293                 *(void **)addrp = bhp->buf;
00294                 goto done;
00295         }
00296 
00297 alloc:  /* Allocate new buffer header and data space. */
00298         if ((ret = CDB___memp_alloc(dbmp,
00299             &dbmp->reginfo[n_cache], mfp, 0, NULL, &bhp)) != 0)
00300                 goto err;
00301 
00302         ++c_mp->stat.st_page_clean;
00303 
00304         /*
00305          * Initialize the BH fields so that we can call the CDB___memp_bhfree
00306          * routine if an error occurs.
00307          */
00308         memset(bhp, 0, sizeof(BH));
00309         bhp->ref = 1;
00310         bhp->pgno = *pgnoaddr;
00311         bhp->mf_offset = mf_offset;
00312 
00313         /* Increment the count of buffers referenced by this MPOOLFILE. */
00314         ++mfp->ref_cnt;
00315 
00316         /*
00317          * Prepend the bucket header to the head of the appropriate MPOOL
00318          * bucket hash list.  Append the bucket header to the tail of the
00319          * MPOOL LRU chain.
00320          */
00321         SH_TAILQ_INSERT_HEAD(&dbht[n_bucket], bhp, hq, __bh);
00322         SH_TAILQ_INSERT_TAIL(&c_mp->bhq, bhp, q);
00323 
00324 #ifdef DIAGNOSTIC
00325         if ((db_alignp_t)bhp->buf & (sizeof(size_t) - 1)) {
00326                 CDB___db_err(dbenv, "Internal error: BH data NOT size_t aligned.");
00327                 ret = EINVAL;
00328                 CDB___memp_bhfree(dbmp, bhp, 1);
00329                 goto err;
00330         }
00331 #endif
00332 
00333         if ((ret = __db_mutex_init(dbenv, &bhp->mutex, R_OFFSET(
00334             dbmp->reginfo, &bhp->mutex) + DB_FCNTL_OFF_MPOOL, 0)) != 0) {
00335                 CDB___memp_bhfree(dbmp, bhp, 1);
00336                 goto err;
00337         }
00338 
00339         /*
00340          * If we created the page, zero it out and continue.
00341          *
00342          * !!!
00343          * Note: DB_MPOOL_NEW specifically doesn't call the pgin function.
00344          * If DB_MPOOL_CREATE is used, then the application's pgin function
00345          * has to be able to handle pages of 0's -- if it uses DB_MPOOL_NEW,
00346          * it can detect all of its page creates, and not bother.
00347          *
00348          * Otherwise, read the page into memory, optionally creating it if
00349          * DB_MPOOL_CREATE is set.
00350          */
00351         if (LF_ISSET(DB_MPOOL_NEW | DB_MPOOL_NEW_GROUP)) {
00352                 if (mfp->clear_len == 0)
00353                         memset(bhp->buf, 0, mfp->stat.st_pagesize);
00354                 else {
00355                         memset(bhp->buf, 0, mfp->clear_len);
00356 #ifdef DIAGNOSTIC
00357                         memset(bhp->buf + mfp->clear_len, CLEAR_BYTE,
00358                             mfp->stat.st_pagesize - mfp->clear_len);
00359 #endif
00360                 }
00361 
00362                 ++mfp->stat.st_page_create;
00363         } else {
00364                 /*
00365                  * It's possible for the read function to fail, which means
00366                  * that we fail as well.  Note, the CDB___memp_pgread() function
00367                  * discards the region lock, so the buffer must be pinned
00368                  * down so that it cannot move and its contents are unchanged.
00369                  */
00370 reread:         if ((ret = CDB___memp_pgread(
00371                     dbmfp, bhp, LF_ISSET(DB_MPOOL_CREATE))) != 0) {
00372                         /*
00373                          * !!!
00374                          * Discard the buffer unless another thread is waiting
00375                          * on our I/O to complete.  Regardless, the header has
00376                          * the BH_TRASH flag set.
00377                          */
00378                         if (bhp->ref == 1)
00379                                 CDB___memp_bhfree(dbmp, bhp, 1);
00380                         goto err;
00381                 }
00382 
00383                 ++mfp->stat.st_cache_miss;
00384         }
00385 
00386         /*
00387          * If we're returning a page after our current notion of the last-page,
00388          * update our information.  Note, there's no way to un-instantiate this
00389          * page, it's going to exist whether it's returned to us dirty or not.
00390          */
00391         if (bhp->pgno > mfp->last_pgno)
00392                 mfp->last_pgno = bhp->pgno;
00393 
00394         *(void **)addrp = bhp->buf;
00395 
00396 done:   /* Update the chain search statistics. */
00397         if (st_hsearch) {
00398                 ++c_mp->stat.st_hash_searches;
00399                 if (st_hsearch > c_mp->stat.st_hash_longest)
00400                         c_mp->stat.st_hash_longest = st_hsearch;
00401                 c_mp->stat.st_hash_examined += st_hsearch;
00402         }
00403 
00404         ++dbmfp->pinref;
00405 
00406         R_UNLOCK(dbenv, dbmp->reginfo);
00407 
00408         return (0);
00409 
00410 err:    /* Discard our reference. */
00411         if (b_incr)
00412                 --bhp->ref;
00413         R_UNLOCK(dbenv, dbmp->reginfo);
00414 
00415         *(void **)addrp = NULL;
00416         return (ret);
00417 }

Generated on Sun Jun 8 10:56:38 2008 for GNUmifluz by  doxygen 1.5.5