    // ------------------------------- //
    // -------- Start of File -------- //
    // ------------------------------- //
    // ----------------------------------------------------------- //
    // C++ Source Code File Name: testprog.cpp
    // Compiler Used: MSVC, BCC32, GCC, HPUX aCC, SOLARIS CC
    // Produced By: glNET Software
    // File Creation Date: 05/17/2000
    // Date Last Modified: 06/12/2001
    // Copyright (c) 2001 glNET Software
    // ----------------------------------------------------------- //
    // ------------- Program Description and Details ------------- //
    // ----------------------------------------------------------- //
    /*
    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
    USA

    This is a test program used demonstrate the use of the database
    engine in a multi-threaded application.
    */
    // ----------------------------------------------------------- //
    #include <iostream.h>
    #include <string.h>
    #include "gxthread.h"
    #include "gxmutex.h"
    #include "gxcond.h"
    #include "gxdbase.h"
    #include "gxdstats.h"

    // Constants
    const int NUM_THREADS = 26;
    const int MAX_NUM_TRY = 3;
    const int name_length = 16;
    const char *name_string = "File Object ";

    struct DatabaseObject {
      char name[name_length];
      int id;
    };

    // Class used to perform multi-threaded reads
    class gxdReadThread : public gxThread
    {
    public:
      gxdReadThread(gxDatabase *gxdfile) { f = gxdfile; curr_offset = (FAU)0; }
      ~gxdReadThread() { }

    private: // Base class interface
      void *ThreadEntryRoutine(gxThread_t *thread);

    private:
      gxDatabase *f;         // Pointer to the open database file
      gxMutex offset_lock;   // Mutex used to serialize access to curr_offset
      FAU curr_offset;       // Current file position following a file read
    };

    // Class used to perform multi-threaded writes
    class gxdWriteThread : public gxThread
    {
    public:
      gxdWriteThread(gxDatabase *gxdfile) { f = gxdfile; }
      ~gxdWriteThread() { }

    private: // Base class interface
      void *ThreadEntryRoutine(gxThread_t *thread);

    private:
      gxMutex write_lock;     // Mutex object used to lock the file
      gxCondition write_cond; // Condition variable used to block other threads
      gxDatabase *f;          // Pointer to the open database file
    };

    void *gxdReadThread::ThreadEntryRoutine(gxThread_t *thread)
    {
      offset_lock.MutexLock(); // Serialize access to curr_offset
      curr_offset = f->FindFirstObject(curr_offset);
      FAU block_address = curr_offset - f->BlockHeaderSize();
      if(curr_offset) {
        DatabaseObject ob;
        f->LockRecord(gxDBASE_READLOCK, block_address);
        f->Read(&ob, sizeof(DatabaseObject), curr_offset);
        cout << "Reading: \"" << ob.name << "\" at address: " << (long)curr_offset
    	 << "\n" << flush;
        f->UnlockRecord(gxDBASE_READLOCK, block_address);
      }
      offset_lock.MutexUnlock();

      return 0;
    }

    void *gxdWriteThread::ThreadEntryRoutine(gxThread_t *thread)
    // Thread safe write function that will not allow access to
    // the critical section until the write operation is complete.
    {
      DatabaseObject *ob = (DatabaseObject *)thread->GetThreadParm();

      write_lock.MutexLock();

      // Tell other threads to wait until this write is complete
      int num_try = 0;
      while(f->LockFile() != 0) {
        // Block this thread from its own execution if a another thread
        // is writing to the file
        if(++num_try < MAX_NUM_TRY) {
          write_cond.ConditionWait(&write_lock);
        }
        else {
          cout << "Could not write object to the file.\n" << flush;
          return 0;
        }
      }

      // ********** Enter Critical Section ******************* //
      f->Write(ob, sizeof(DatabaseObject), f->Alloc(sizeof(DatabaseObject)));
      // ********** Leave Critical Section ******************* //

      f->UnlockFile(); // Tell other threads that this write is complete

      // Wake up the next thread waiting on this condition
      write_cond.ConditionSignal();
      write_lock.MutexUnlock();

      return 0;
    }

    int main()
    {
      gxDatabase *f = new gxDatabase;
      const char *fname = "testfile.gxd";
      f->Create(fname, FAU(0), 'C'); // Persistent lock revision
      if(CheckError(f) != 0) return 1;

      // Initialize the multi-threaded database objects
      gxdReadThread *read_thread = new gxdReadThread(f);
      gxdWriteThread *write_thread = new gxdWriteThread(f);

      // Arrays used to hold the read and write threads
      gxThread_t *wthreads[NUM_THREADS];
      gxThread_t *rthreads[NUM_THREADS];

      int i, j;
      cout << "Writing " << NUM_THREADS << " objects to the " << fname << " file"
           << endl;
      DatabaseObject *ob_ptr[NUM_THREADS];

      for(i = 0; i < NUM_THREADS; i++) {
        DatabaseObject *ob = new DatabaseObject; // Persistent object
        ob->id = 65+i;
        for(j = 0; j < name_length; j++) ob->name[j] = 0;
        memmove(ob->name, name_string, strlen(name_string));
        ob->name[strlen(name_string)] = char(ob->id);
        wthreads[i] = write_thread->CreateThread((void *)ob);
        ob_ptr[i] = ob;
      }

      for(i = 0; i < NUM_THREADS; i++) write_thread->JoinThread(wthreads[i]);
      cout << "Write complete" << endl;

      cout << "Verifing each object" << endl;
      cout << "Press Enter to continue..." << endl;
      cin.get();
      for(i = 0; i < NUM_THREADS; i++) {
        rthreads[i] = read_thread->CreateThread();
        read_thread->sSleep(1); // Allow each thread time to print its message
      }

      // Wait for the read threads to complete
      for(i = 0; i < NUM_THREADS; i++) read_thread->JoinThread(rthreads[i]);

      // Cleanup and release all the thread resources
      for(i = 0; i < NUM_THREADS; i++) write_thread->DestroyThread(wthreads[i]);
      for(i = 0; i < NUM_THREADS; i++) read_thread->DestroyThread(rthreads[i]);

      delete write_thread; // Release the write object's file pointer
      delete read_thread;  // Release the read object's file pointer

      f->Close(); // Close the file
      if(CheckError(f) != 0) return 1;
      delete f;

      // Free the memory allocated for each database object pointer
      for(i = 0; i < NUM_THREADS; i++) delete ob_ptr[i];

      return 0;
    }
    // ----------------------------------------------------------- //
    // ------------------------------- //
    // --------- End of File --------- //
    // ------------------------------- //