// ------------------------------- //
// -------- 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 --------- //
// ------------------------------- //