    // ------------------------------- //
    // -------- Start of File -------- //
    // ------------------------------- //
    // ----------------------------------------------------------- //
    // C++ Source Code File Name: testprog.cpp
    // Compiler Used: MSVC, BCC32, GCC, HPUX aCC, SOLARIS CC
    // Produced By: DataReel Software Development Team
    // File Creation Date: 05/17/2000
    // Date Last Modified: 06/17/2016
    // Copyright (c) 2001-2024 DataReel Software Development
    // ----------------------------------------------------------- //
    // ------------- 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 32/64-bit
    database engine in a multi-threaded application.

    This example demonstrates file and record locking used with
    single process threads. File and record locking via persistent
    locks or lock headers are not required for single process threads
    but are used here to demonstrate how file and record locking
    could be used a multi-process/multi-machine environment.
    Persistent locks provide a platform independent locking mechanism
    signaling to threads in other process and processes running on
    remote machines that the file or record is locked. The
    multi-process thread or remote process is responsible for
    reading the lock and acting accordingly. Single process threads
    can omit persistent locks thorough the use of mutex locks and
    condition variables.
    */
    // ----------------------------------------------------------- //
    #include "gxdlcode.h"

    #if defined (__USE_ANSI_CPP__) // Use the ANSI Standard C++ library
    #include <iostream>
    using namespace std; // Use unqualified names for Standard C++ library
    #else // Use the old iostream library by default
    #include <iostream.h>
    #endif // __USE_ANSI_CPP__

    #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_t)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_t 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_t 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(int argv, char **argc)
    {
      char rev_letter = 'D'; // Set the revision letter
      if(argv == 2) { // Set a specified revision letter
        rev_letter = *argc[1];
        if(rev_letter == '0') rev_letter = 0;
        // Valid persistent record lock rev letters are:
        // Rev 'C' or 'c'
        // Rev 'D' or 'd'
      }

      gxDatabase *f = new gxDatabase;
      const char *fname = "testfile.gxd";
      f->Create(fname, (FAU_t)0, rev_letter); // Persistent lock revision
      if(CheckError(f) != 0) {
        delete f;
        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"
           << "\n";
      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" << "\n";

      cout << "Verifing each object" << "\n";
      cout << "Press Enter to continue..." << "\n";
      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) {
        delete f;
        return 1;
      }

      // Free the memory allocated for the file pointer
      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 --------- //
    // ------------------------------- //