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