Main Page | Namespace List | Class Hierarchy | Class List | Directories | File List | Namespace Members | Class Members | File Members

ShortIdImpl.C

Go to the documentation of this file.
00001 // Copyright (C) 2001, Compaq Computer Corporation
00002 // 
00003 // This file is part of Vesta.
00004 // 
00005 // Vesta is free software; you can redistribute it and/or
00006 // modify it under the terms of the GNU Lesser General Public
00007 // License as published by the Free Software Foundation; either
00008 // version 2.1 of the License, or (at your option) any later version.
00009 // 
00010 // Vesta is distributed in the hope that it will be useful,
00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013 // Lesser General Public License for more details.
00014 // 
00015 // You should have received a copy of the GNU Lesser General Public
00016 // License along with Vesta; if not, write to the Free Software
00017 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00018 
00019 //
00020 // ShortIdImpl.C
00021 // Last modified on Mon Dec  5 17:49:09 EST 2005 by ken@xorian.net   
00022 //      modified on Tue Aug  7 19:40:28 PDT 2001 by mann   
00023 //      modified on Tue May  4 10:50:51 PDT 1999 by heydon 
00024 //
00025 // Server implementations of remote methods in ShortIdBlock.H
00026 // 
00027 
00028 #if __linux__
00029 #include <stdint.h>
00030 #endif
00031 #include <sys/types.h>
00032 #include <dirent.h>
00033 // add declaration to fix broken <dirent.h> header file
00034 extern "C" int _Preaddir_r(DIR *, struct dirent *, struct dirent **);
00035 
00036 #include <pthread.h>
00037 #include <time.h>
00038 #include <stdlib.h>
00039 #include <sys/stat.h>
00040 #include <ctype.h>
00041 
00042 #include <VestaConfig.H>
00043 #include <Thread.H>
00044 
00045 #include "ShortIdImpl.H"
00046 #include "VestaLog.H"
00047 #include "Recovery.H"
00048 #include "ReadersWritersLock.H"
00049 #include "ShortIdKey.H"
00050 #include "FdCache.H"
00051 #include "VRConcurrency.H"
00052 #include "logging.H"
00053 
00054 #include "lock_timing.H"
00055 
00056 using std::fstream;
00057 using std::hex;
00058 using std::dec;
00059 
00060 // Pitfall: on Tru64 random's state is per-thread, so it would be
00061 // difficult to make sure it gets reseeded (with a distinct seed!)
00062 // in every thread.  Instead we use the less-standard random_r function.
00063 #define USE_RANDOM_R 1
00064 
00065 // Types
00066 struct ShortIdBlockInfo {
00067     time_t leaseExpires;
00068 };
00069 typedef Table<ShortIdKey, ShortIdBlockInfo*>::Default BlockInfoTable;
00070 typedef Table<ShortIdKey, ShortIdBlockInfo*>::Iterator BlockInfoIter;
00071 
00072 // Parameters
00073 const int LEASE_PERIOD = 60*60*24;  // validity period for leases (sec)
00074 const int LANDLORD_SLEEP = 60*60;   // period between lease checks (sec)
00075 const int LANDLORD_WORKLIST_SIZE = 128;
00076 
00077 // Module globals
00078 static BlockInfoTable biTable;
00079 #if USE_RANDOM_R
00080 /*static*/ struct random_data randDat;
00081 /*static*/ int randState[32];
00082 #endif
00083 static Basics::mutex mu;          // protects biTable, randDat
00084 static Basics::thread landlord;   // detects expired leases
00085 static Text sid_dir;
00086 
00087 // RPC to get a new block of ShortIds from the server, all with the
00088 // specified leafflag.  On return, the block is leased to the caller
00089 // until bk.leaseExpires (set by the function).  If the lease expires
00090 // without being renewed (below), the server will eventually notice
00091 // and reclaim all unused ShortIds in the block.  If local is true,
00092 // the caller is in the same address space as the server, so the lease
00093 // is nonexpiring and is not logged---if the server crashes, the
00094 // caller crashes too, and no longer needs the lease.
00095 void
00096 AcquireShortIdBlock(ShortIdBlock& bk, bool leafflag, bool local) throw()
00097 {
00098     ShortId start;
00099     int used, used2 = -1;
00100     ShortIdBlock bk2;
00101     
00102     if (!local) {
00103         StableLock.acquireWrite(); // protects the log
00104         RWLOCK_LOCKED_REASON(&StableLock, "AcquireShortIdBlock");
00105     }
00106     mu.lock();
00107     for (;;) {
00108         // Randomly choose a nonzero start for the block
00109         int randint;
00110 #if USE_RANDOM_R
00111 # if __digital__
00112         (void) random_r(&randint, &randDat);
00113 # else
00114         (void) random_r(&randDat, &randint);
00115 # endif
00116 #else
00117         randint = random();
00118 #endif
00119         start = (ShortId)
00120           ( (randint & ~(ShortIdBlock::size - 1) &
00121              ~ShortIdBlock::leafFlag & ~ShortIdBlock::dirFlag)
00122            | (leafflag ? ShortIdBlock::leafFlag : 0) );
00123         if (start == NullShortId) {
00124             continue;
00125         }
00126         ShortIdBlockInfo* dummy;
00127         if (biTable.Get(start, dummy)) {
00128             // Some other process is using this block; try another
00129             continue;
00130         }
00131 
00132         // Check that there are some free ShortIds there
00133         bk.init(start);
00134         used = 0;
00135         char *name = ShortIdBlock::shortIdToName(start);
00136         char *p = strrchr(name, PathnameSep);
00137         assert(p != 0);
00138         *p = '\0';
00139         DIR *dir = opendir(name);
00140         delete[] name;
00141         if (!dir) {
00142             // Directory not present, so all of block is free
00143             break;
00144         }
00145         struct dirent de, *done;
00146         while (readdir_r(dir, /*OUT*/ &de, /*OUT*/ &done) == 0 &&
00147                done != (struct dirent *)NULL) {
00148             char *endptr;
00149             long offset = strtol(de.d_name, &endptr, 16);
00150             if (*endptr != '\0') continue;
00151             bk.set(start + offset);
00152             used++;
00153         }
00154         closedir(dir);
00155         if (used >= ShortIdBlock::size) {
00156             // Nothing free in this block; try another
00157             continue;
00158         }
00159         if (used2 == -1 && used > ShortIdBlock::size / 2) {
00160             // Block more than half full; save it and try another
00161             bk2 = bk;
00162             used2 = used;
00163             continue;
00164         } else {
00165             break;
00166         }
00167     }
00168     if (used2 != -1) {
00169         if (used2 < used) {
00170             // First one tried was less full!
00171             bk = bk2;
00172         }
00173     }
00174     ShortIdKey key(start);
00175     ShortIdBlockInfo* bi = NEW(ShortIdBlockInfo);
00176     bk.leaseExpires = bi->leaseExpires =
00177       (local ? ShortIdBlock::leaseNonexpiring : time(NULL) + LEASE_PERIOD);
00178     biTable.Put(key, bi);
00179     if (!local) {
00180         char logrec[256];
00181         // ('asidb' start leaseExpires)
00182         int sres = sprintf(logrec, "(asidb 0x%x %d)\n",
00183                            start, bk.leaseExpires);
00184         assert(sres > 0);
00185         assert(sres < sizeof(logrec));
00186         VRLog.start();
00187         VRLog.put(logrec);
00188         VRLog.commit();
00189     }
00190     mu.unlock();
00191     if (!local) {
00192         StableLock.releaseWrite();
00193     }
00194 }
00195 
00196 
00197 // RPC to renew the lease on a block.  Returns false if the block's
00198 // lease had already expired.  (Allowing a lease to expire is a fatal
00199 // client error.)  Otherwise, returns true, and the block is leased to
00200 // the caller until block.leaseExpires (set by the function).  This
00201 // routine is not called for local (nonexpiring) leases.
00202 //
00203 bool
00204 RenewShortIdBlock(ShortIdBlock& bk) throw()
00205 {
00206     StableLock.acquireWrite();
00207     RWLOCK_LOCKED_REASON(&StableLock, "RenewShortIdBlock");
00208     mu.lock();
00209     ShortIdKey key(bk.start);
00210     ShortIdBlockInfo* bi;
00211     bool inTable = biTable.Delete(key, bi); // reuse old bi value
00212     time_t now = time(0);
00213     if (!inTable || bi->leaseExpires < now) {
00214         mu.unlock();
00215         StableLock.releaseWrite();
00216         if(inTable) delete bi;
00217         return false;
00218     }
00219     bk.leaseExpires = bi->leaseExpires = now + LEASE_PERIOD;
00220     biTable.Put(key, bi);
00221     char logrec[256];
00222     // ('asidb' start leaseExpires)
00223     int sres = sprintf(logrec, "(asidb 0x%x %d)\n",
00224                        bk.start, bk.leaseExpires);
00225     assert(sres > 0);
00226     assert(sres < sizeof(logrec));
00227     VRLog.start();
00228     VRLog.put(logrec);
00229     VRLog.commit();
00230     mu.unlock();
00231     StableLock.releaseWrite();
00232     return true;
00233 }
00234 
00235 
00236 static void
00237 ReleaseShortIdBlockAt(ShortId start, bool local) throw()
00238 {
00239     ShortIdBlockInfo* bi;
00240     bool ok = biTable.Delete(start, bi);
00241     if (ok) delete bi;
00242     if (!local) {
00243         char logrec[256];
00244         // ('rsidb' start)
00245         int sres = sprintf(logrec, "(rsidb 0x%x)\n", start);
00246         assert(sres > 0);
00247         assert(sres < sizeof(logrec));
00248         VRLog.start();
00249         VRLog.put(logrec);
00250         VRLog.commit();
00251     }
00252 }
00253 
00254 // RPC to return a block to the server
00255 // The caller promises not to assign any more ShortIds from this block, even
00256 //  if some are still unused.  This routine should be called when a process
00257 //  is going to shut down, or when it has used up a block. If local is true,
00258 //  the caller is in the same address space as the server, so the lease
00259 //  is not logged---if the server crashes, the caller crashes too, and
00260 //  no longer needs the lease.
00261 //
00262 void
00263 ReleaseShortIdBlock(ShortIdBlock& bk, bool local) throw()
00264 {
00265     if (!local) {
00266         StableLock.acquireWrite();      // protects the log too
00267         RWLOCK_LOCKED_REASON(&StableLock, "ReleaseShortIdBlock");
00268     }
00269     mu.lock();
00270     ReleaseShortIdBlockAt(bk.start, local);
00271     mu.unlock();
00272     if (!local) {
00273         StableLock.releaseWrite();
00274     }
00275 }
00276 
00277 
00278 // 
00279 // Thread to clean up blocks with expired leases
00280 //
00281 // Annoyingly, one can't delete from a Table while in the scope
00282 // of an interator.  We would really like to delete the element
00283 // the iterator has just returned sometimes.  Instead, we make an
00284 // array of things to delete and do them later.  If the array fills,
00285 // we get the rest on the next run.
00286 //
00287 // [Note: Tables have been since been improved to provide a way of
00288 //  deleting while in the scope of an iterator, but the code below
00289 //  has not been rewritten to take advantage of the new feature.]
00290 //
00291 void *
00292 LandlordThread(void *arg)
00293 {
00294     signal(SIGPIPE, SIG_IGN);
00295     signal(SIGQUIT, SIG_DFL);
00296     signal(SIGSEGV, SIG_DFL);
00297     signal(SIGABRT, SIG_DFL);
00298     signal(SIGILL, SIG_DFL);
00299     signal(SIGBUS, SIG_DFL);
00300 
00301     for (;;) {
00302         ShortId worklist[LANDLORD_WORKLIST_SIZE];
00303         int i = 0;
00304         StableLock.acquireWrite();
00305         RWLOCK_LOCKED_REASON(&StableLock, "LandlordThread");
00306         mu.lock();
00307         {
00308             BlockInfoIter iter(&biTable);
00309             ShortIdKey key;
00310             ShortIdBlockInfo* bi;
00311             time_t now = time(NULL);
00312             while (iter.Next(key, bi)) {
00313                 if (bi->leaseExpires < now) {
00314                     worklist[i++] = key.sid;
00315                     if (i >= LANDLORD_WORKLIST_SIZE) break;
00316                 }
00317             }
00318         }
00319         int j;
00320         for (j=0; j<i; j++) {
00321             ReleaseShortIdBlockAt(worklist[j], false);
00322         }
00323         mu.unlock();
00324         StableLock.releaseWrite();
00325         sleep(LANDLORD_SLEEP);
00326     }
00327 
00328     //return (void *)NULL;     // Not reached
00329 }
00330 
00331 // Code to rebuild biTable from the log
00332 static void
00333 AsidbCallback(RecoveryReader* rr, char &c)
00334      throw(VestaLog::Error, VestaLog::Eof)
00335 {
00336     long ltmp;
00337     ShortId start;
00338     time_t leaseExpires;
00339     RecoveryReader::Ident id;
00340     
00341     // start leaseExpires
00342     rr->getLong(c, ltmp);
00343     start = (ShortId) ltmp;
00344     rr->getLong(c, ltmp);
00345     leaseExpires = (ShortId) ltmp;
00346     
00347     ShortIdKey key(start);
00348     
00349     ShortIdBlockInfo* bi;
00350     bool inTable = biTable.Delete(key, bi);
00351     if(!inTable)
00352       bi = NEW(ShortIdBlockInfo);
00353     else
00354       assert(bi != 0);
00355     bi->leaseExpires = leaseExpires;
00356     mu.lock();
00357     inTable = biTable.Put(key, bi);
00358     assert(!inTable);
00359     mu.unlock();
00360 }
00361 
00362 static void
00363 RsidbCallback(RecoveryReader* rr, char &c)
00364      throw(VestaLog::Error, VestaLog::Eof)
00365 {
00366     ShortId start;
00367     long lstart;
00368     
00369     // start
00370     rr->getLong(c, lstart);
00371     start = (ShortId) lstart;
00372     mu.lock();
00373     ShortIdBlockInfo* bi;
00374     bool ok = biTable.Delete(start, bi);
00375     if (ok) delete bi;
00376     mu.unlock();
00377 }
00378 
00379 // Code to do checkpointing
00380 void
00381 ShortIdBlockCheckpoint(fstream& ckpt) throw()
00382 {
00383     // VRLock.write is already held
00384     mu.lock();
00385     BlockInfoIter iter(&biTable);
00386     ShortIdKey key;
00387     ShortIdBlockInfo* bi;
00388 
00389     while (iter.Next(key, bi)) {
00390         ckpt << "(asidb 0x" << hex << key.sid << dec
00391           << " " << bi->leaseExpires << ")\n";
00392     }
00393 
00394     mu.unlock();
00395 }
00396 
00397 
00398 static const int charsPerArc[] = { 3, 3, 2, 0 };
00399 static const int MAX_CHARS_PER_ARC = 3;
00400 
00401 extern "C"
00402 {
00403   static int
00404   arccmp(const void* a, const void* b)
00405   {
00406     return strcmp((char*) a, (char*) b);
00407   }
00408 }
00409 
00410 static ShortId
00411 NextSidToKeep(SourceOrDerived& sidstream)
00412 {
00413     ShortId nextkeep;
00414 
00415     if (sidstream.eof()) {
00416         Repos::dprintf(DBG_SIDDEL, "at end of ShortIdsFile\n");
00417         nextkeep = NullShortId;
00418     } else {
00419         for (;;) {
00420             sidstream >> nextkeep;
00421             if (sidstream.fail()) {
00422                 assert(sidstream.eof());
00423                 Repos::dprintf(DBG_SIDDEL, "hit end of ShortIdsFile\n");
00424                 nextkeep = NullShortId;
00425             } else if (nextkeep == NullShortId) {
00426                 Repos::dprintf(DBG_ALWAYS, "error: NullShortId on keep list\n");
00427                 continue; // skip the null
00428             }
00429             Repos::dprintf(DBG_SIDDEL, "next to keep: 0x%08x\n", nextkeep);
00430             break;
00431         }
00432     }
00433     return nextkeep;
00434 }
00435 
00436 // Scan a directory level in ShortId storage.  Return the number of
00437 // entries remaining.  On error, return -(errno).  Delete any ShortIds
00438 // not listed in the sidfile, and any subdirectories that are emptied.
00439 static int
00440 ScanDirForDelete(int levelnum, const char* dirname, Bit32 dirnum,
00441                  ShortId& nextkeep, SourceOrDerived& sidstream, time_t lease)
00442 {
00443     typedef char SidArc[MAX_CHARS_PER_ARC + 1];
00444     SidArc *arcs = NEW_PTRFREE_ARRAY(SidArc, 1 << (MAX_CHARS_PER_ARC * 4));
00445     int narcs = 0;
00446     int i, nkept, ret;
00447     DIR* dir = opendir(dirname);
00448     if (dir == NULL) {
00449         Repos::dprintf(DBG_ALWAYS, "error opening sid directory, errno %d\n", errno);
00450         assert(errno != 0);
00451         return -errno;
00452     }
00453     struct dirent de, *done;
00454     while (readdir_r(dir, /*OUT*/ &de, /*OUT*/ &done) == 0 &&
00455            done != (struct dirent *)NULL) {
00456         bool ok = true;
00457         for (i = 0; i < charsPerArc[levelnum]; i++) {
00458             if (!isxdigit(de.d_name[i]) || isupper(de.d_name[i])) {
00459                 ok = false;
00460                 break;
00461             }
00462         }
00463         if (!ok || de.d_name[charsPerArc[levelnum]] != '\000') continue;
00464         strcpy(arcs[narcs++], de.d_name);
00465     }
00466     closedir(dir);
00467     if (narcs == 0) return 0;
00468     qsort(arcs, narcs, MAX_CHARS_PER_ARC + 1, arccmp);
00469     nkept = narcs;
00470     if (charsPerArc[levelnum + 1] == 0) {
00471         // At bottom level, can do file deletions
00472         for (i = 0; i < narcs; i++) {
00473             ShortId cursid = (dirnum << (charsPerArc[levelnum]*4)) +
00474               strtoul(arcs[i], NULL, 16);
00475             for (;;) {
00476                 if (nextkeep != NullShortId && cursid > nextkeep) {
00477                     if (SourceOrDerived::dirShortId(nextkeep)) {
00478                         Repos::dprintf(DBG_SIDDISP, "directory: 0x%08x\n", nextkeep);
00479                     } else {
00480                         Repos::dprintf(DBG_ALWAYS, "missing: 0x%08x\n", nextkeep);
00481                     }
00482                     nextkeep = NextSidToKeep(sidstream);
00483                     continue;
00484                 }
00485                 break;
00486             }
00487             if (nextkeep == cursid) {
00488                 Repos::dprintf(DBG_SIDDISP, "listed: 0x%08x\n", cursid);
00489                 nextkeep = NextSidToKeep(sidstream);
00490             } else {
00491                 // cursid < nextkeep || nextkeep == NullShortId
00492                 char *sidname = NEW_PTRFREE_ARRAY(char, (strlen(dirname) + 1 +
00493                                                          strlen(arcs[i]) + 1));
00494                 if (*dirname == '\0') {
00495                     strcpy(sidname, arcs[i]);
00496                 } else {
00497                     sprintf(sidname, "%s%c%s",
00498                             dirname, PathnameSep, arcs[i]);
00499                 }
00500                 struct stat statbuf;
00501                 int res = stat(sidname, &statbuf);
00502                 if (res < 0) {
00503                     if (errno == ENOENT) {
00504                       // Harmless race with eager deletion of this sid
00505                       nkept--;
00506                       delete [] sidname;
00507                       continue;
00508                     }
00509                     Repos::dprintf(DBG_ALWAYS,
00510                                    "error on stat of sid, errno %d\n", errno);
00511                     assert(errno != 0);
00512                     delete [] sidname;
00513                     return -errno;
00514                 }
00515                 if (statbuf.st_ctime < lease) {
00516                     Repos::dprintf(DBG_SIDDISP, "garbage: 0x%08x\n", cursid);
00517                     if (!Repos::isDebugLevel(DBG_SIDNODEL)) {
00518                         res = unlink(sidname);
00519                         if (res < 0 && errno != ENOENT) {
00520                             Repos::dprintf(DBG_ALWAYS,
00521                                            "error on unlink of sid, errno %d\n", errno);
00522                             assert(errno != 0);
00523                             delete [] sidname;
00524                             return -errno;
00525                         }
00526                     }
00527                     FdCache::flush(cursid, FdCache::any);
00528                     nkept--;
00529                 } else {
00530                     Repos::dprintf(DBG_SIDDISP, "leased: 0x%08x\n", cursid);
00531                 }
00532                 delete [] sidname;
00533             }
00534         }
00535     } else {
00536         for (i = 0; i < narcs; i++) {
00537             char *subdirname = NEW_PTRFREE_ARRAY(char, (strlen(dirname) + 1 +
00538                                                         strlen(arcs[i]) + 1));
00539             Bit32 subdirnum = (dirnum << (charsPerArc[levelnum]*4)) +
00540               strtoul(arcs[i], NULL, 16);
00541             if (*dirname == '\0') {
00542                 strcpy(subdirname, arcs[i]);
00543             } else {
00544                 sprintf(subdirname, "%s%c%s",
00545                         dirname, PathnameSep, arcs[i]);
00546             }
00547             ret = ScanDirForDelete(levelnum + 1, subdirname, subdirnum,
00548                                    nextkeep, sidstream, lease);
00549             if (ret < 0) {
00550               delete [] subdirname;
00551               return ret;
00552             }
00553             if (ret == 0) {
00554                 Repos::dprintf(DBG_SIDDISP, "empty: %s\n", subdirname);
00555                 if (!Repos::isDebugLevel(DBG_SIDNODEL)) {
00556                     int res = rmdir(subdirname);
00557                     if (res < 0) {
00558                         Repos::dprintf(DBG_ALWAYS, "error on sid rmdir, errno %d\n",
00559                                        errno);
00560                         assert(errno != 0);
00561                         delete [] subdirname;
00562                         return -errno;
00563                     }
00564                 }
00565                 nkept--;
00566             }
00567             delete [] subdirname;
00568         }
00569     }
00570     delete [] arcs;
00571     return nkept;
00572 }
00573 
00574 
00575 // Do actual deletions, at the end of a weed.
00576 // sidfile must be sorted.
00577 int
00578 DeleteAllShortIdsBut(ShortIdsFile sidfile, time_t lease) throw()
00579 {
00580     ShortId nextkeep;
00581     SourceOrDerived sidstream;
00582     sidstream.open(sidfile);
00583     sidstream >> hex;
00584     if (!sidstream.good()) {
00585         int ret = errno;
00586         Repos::dprintf(DBG_ALWAYS,
00587                        "error opening combined keepSidFile, errno %d\n", ret);
00588         assert(ret != 0);
00589         sidstream.close();
00590         return ret;
00591     }
00592     // Prime the pump
00593     nextkeep = NextSidToKeep(sidstream);
00594     int ret =
00595       ScanDirForDelete(0, sid_dir.cchars(), 0, nextkeep, sidstream, lease); 
00596     sidstream.close();
00597     if (ret < 0) return -ret;
00598     return 0;
00599 }
00600 
00601 
00602 void
00603 ShortIdServerInit()
00604 {
00605 #if USE_RANDOM_R
00606     memset((void *) randState, 0, sizeof(randState));
00607     randDat.state = NULL;
00608     char *dummy;
00609 # if __digital__
00610     (void) initstate_r((unsigned) time(0),
00611                        (char *) randState, sizeof(randState),
00612                        &dummy, &randDat);
00613 # else
00614     (void) initstate_r((unsigned) time(0),
00615                        (char *) randState, sizeof(randState),
00616                        &randDat);
00617 # endif
00618 #else
00619     srandom((unsigned) time(NULL));
00620 #endif //NOTDEF
00621     RegisterRecoveryCallback("asidb", AsidbCallback);
00622     RegisterRecoveryCallback("rsidb", RsidbCallback);
00623     try {
00624         bool ok = VestaConfig::get("Repository", "sid_dir", sid_dir);
00625         if (!ok) sid_dir = "";
00626     } catch (VestaConfig::failure f) {
00627         Repos::dprintf(DBG_ALWAYS, "VestaConfig::failure %s\n", f.msg.cchars());
00628         abort();
00629     }
00630 }
00631 
00632 void
00633 ShortIdServerInit2()
00634 {
00635   Basics::thread_attr landlord_attr;
00636 #if defined (_POSIX_THREAD_PRIORITY_SCHEDULING) && !defined(__linux__)
00637   // Linux only allows the superuser to use SCHED_RR
00638   landlord_attr.set_schedpolicy(SCHED_RR);
00639   landlord_attr.set_inheritsched(PTHREAD_EXPLICIT_SCHED);
00640   landlord_attr.set_sched_priority(sched_get_priority_min(SCHED_RR));
00641 #endif
00642   
00643   landlord.fork(LandlordThread, 0, landlord_attr);
00644 }

Generated on Mon May 8 00:48:46 2006 for Vesta by  doxygen 1.4.2