Isis 3 Programmer Reference
CubeDataThread.cpp
1 #include "IsisDebug.h"
2 #include "CubeDataThread.h"
3 
4 #include <QApplication>
5 #include <QEventLoop>
6 #include <QMap>
7 #include <QMutex>
8 #include <QPair>
9 #include <QReadWriteLock>
10 #include <QString>
11 
12 #include <iomanip>
13 #include <iostream>
14 
15 #include "Brick.h"
16 #include "Cube.h"
17 #include "FileName.h"
18 #include "IException.h"
19 #include "IString.h"
20 #include "UniversalGroundMap.h"
21 
22 namespace Isis {
30  p_managedCubes = NULL;
31  p_managedData = NULL;
32  p_threadSafeMutex = NULL;
33  p_managedDataSources = NULL;
34 
37  p_threadSafeMutex = new QMutex();
39 
42  p_currentId = 1; // start with ID == 1
43  p_stopping = false;
44 
45  // Start this thread's event loop and make it so the slots are contained
46  // within this thread (this class automatically runs in its own thread)
47  start();
48  moveToThread(this);
49 
50  }
51 
52 
61  // Shutdown the event loop(s)
62  QThread::exit(0);
63 
64  p_stopping = true;
65 
66  while (!isFinished()) {
67  QThread::yieldCurrentThread();
68  }
69 
70  // Destroy the bricks still in memory
71  if (p_managedData) {
72  for (int i = p_managedData->size() - 1; i >= 0; i--) {
73  delete (*p_managedData)[i].first;
74  delete (*p_managedData)[i].second;
75  p_managedData->removeAt(i);
76  }
77 
78  delete p_managedData;
79  p_managedData = NULL;
80  }
81 
82  // Destroy the cubes still in memory
83  if (p_managedCubes) {
84  for (int i = p_managedCubes->size() - 1; i >= 0; i--) {
85  if ((p_managedCubes->end() - 1)->first) // only delete if we own it!
86  delete (p_managedCubes->end() - 1).value().second;
87  p_managedCubes->erase(p_managedCubes->end() - 1);
88  }
89  }
90 
91  // Destroy the mutex that controls access to the cubes
92  if (p_threadSafeMutex) {
93  delete p_threadSafeMutex;
94  p_threadSafeMutex = NULL;
95  }
96 
97  // Destroy the data sources vector
99  delete p_managedDataSources;
100  p_managedDataSources = NULL;
101  }
102  }
103 
119  int CubeDataThread::AddCube(const FileName &fileName,
120  bool mustOpenReadWrite) {
121  Cube *newCube = new Cube();
122 
123  try {
124  newCube->open(fileName.expanded(), "rw");
125  }
126  catch (IException &e) {
127  if (!mustOpenReadWrite) {
128  newCube->open(fileName.expanded(), "r");
129  }
130  else {
131  throw;
132  }
133  }
134 
135  p_threadSafeMutex->lock();
136 
137  int newId = p_currentId;
138  p_currentId++;
139 
140  QPair< bool, Cube * > newEntry;
141  newEntry.first = true; // we own this!
142  newEntry.second = newCube;
143  p_managedCubes->insert(newId, newEntry);
144 
145  p_threadSafeMutex->unlock();
146 
147  return newId;
148  }
149 
163  p_threadSafeMutex->lock();
164 
165  int newId = p_currentId;
166  p_currentId++;
167 
168  QPair< bool, Cube * > newEntry;
169  newEntry.first = false; // we don't own this!
170  newEntry.second = cube;
171  p_managedCubes->insert(newId, newEntry);
172 
173  p_threadSafeMutex->unlock();
174 
175  return newId;
176  }
177 
178 
186  void CubeDataThread::RemoveCube(int cubeId) {
187  p_threadSafeMutex->lock();
188 
189  QMap< int, QPair< bool, Cube * > >::iterator i;
190  i = p_managedCubes->find(cubeId);
191 
192  if (i == p_managedCubes->end()) {
193  p_threadSafeMutex->unlock();
194  QString msg = "CubeDataThread::RemoveCube failed because cube ID [";
195  msg += QString::number(cubeId);
196  msg += "] not found";
198  }
199 
200  if (p_managedDataSources->contains(cubeId)) {
201  p_threadSafeMutex->unlock();
202  QString msg = "CubeDataThread::RemoveCube failed cube ID [";
203  msg += QString::number(cubeId);
204  msg += "] has requested Bricks";
206  }
207 
208  // if we have ownership of the cube then me must delete it
209  if (i.value().first)
210  delete i.value().second;
211  i.value().second = NULL;
212 
213  p_managedCubes->remove(i.key());
214 
215  p_threadSafeMutex->unlock();
216 
217  }
218 
219 
226  }
227 
234  }
235 
252  void CubeDataThread::GetCubeData(int cubeId, int ss, int sl, int es, int el,
253  int band, void *caller, bool sharedLock) {
254 
255  Brick *requestedBrick = NULL;
256 
257  p_threadSafeMutex->lock();
258  requestedBrick = new Brick(*p_managedCubes->value(cubeId).second, es
259  - ss + 1, el - sl + 1, 1);
260  requestedBrick->SetBasePosition(ss, sl, band);
261  p_threadSafeMutex->unlock();
262 
263  // See if we already have this brick
264  int instance = 0;
265  int exactIndex = -1;
266  bool exactMatch = false;
267  int index = OverlapIndex(requestedBrick, cubeId, instance, exactMatch);
268 
269  // while overlaps are found
270  while (index != -1) {
271  if (sharedLock) {
272  // make sure we can get read locks on exact overlaps
273  // We need to try to get the lock to verify partial overlaps not
274  // write locked and only keep read locks on exact matches.
275  AcquireLock((*p_managedData)[index].first, true);
276  if (!exactMatch) {
277  (*p_managedData)[index].first->unlock();
278  }
279  }
280  else {
281  AcquireLock((*p_managedData)[index].first, false);
282  // we arent actually writing to this, but now we know we can delete it
283  (*p_managedData)[index].first->unlock();
284 
285  exactMatch = false;
286 
287  // destroy things that overlap but arent the same when asking to write
288  if (FreeBrick(index)) {
289  instance--;
290  }
291  }
292 
293  if (exactMatch) {
294  exactIndex = index;
295  }
296 
297  instance++;
298  index = OverlapIndex(requestedBrick, cubeId, instance, exactMatch);
299  }
300 
301  if(p_stopping) return;
302 
303  if (exactIndex == -1) {
304  p_threadSafeMutex->lock();
305 
306  p_managedCubes->value(cubeId).second->read(*requestedBrick);
307 
308  QPair< QReadWriteLock *, Brick * > managedDataEntry;
309 
310  managedDataEntry.first = new QReadWriteLock();
311 
312  AcquireLock(managedDataEntry.first, sharedLock);
313 
314  managedDataEntry.second = requestedBrick;
315 
316  p_managedData->append(managedDataEntry);
317  p_managedDataSources->append(cubeId);
318 
319  exactIndex = p_managedData->size() - 1;
320 
321  p_threadSafeMutex->unlock();
322  }
323 
324  if (el - sl + 1 != (*p_managedData)[exactIndex].second->LineDimension())
325  {
326  //abort();
327  }
328 
329  if (sharedLock) {
330  emit ReadReady(caller, cubeId, (*p_managedData)[exactIndex].second);
331  }
332  else {
333  emit ReadWriteReady(caller, cubeId, (*p_managedData)[exactIndex].second);
334  }
335  }
336 
337 
345  int CubeDataThread::FindCubeId(const Cube * cubeToFind) const {
346  QMapIterator< int, QPair< bool, Cube * > > i(*p_managedCubes);
347  while (i.hasNext()) {
348  i.next();
349  if (i.value().second == cubeToFind)
350  return i.key();
351  }
353  "Cube does not exist in this CubeDataThread", _FILEINFO_);
354  }
355 
356 
365  void CubeDataThread::AcquireLock(QReadWriteLock *lockObject, bool readLock) {
366  // This method can be called recursively (sorta). In the "recursive"
367  // cases p_currentLockWaiting is > 0. This guarantees that locks are
368  // aquired in the reverse order as they are requested, which isn't
369  // particularly helpful, but it is consistent :)
370 
371  if (readLock) {
372  while (!lockObject->tryLockForRead()) {
373  // while we can't get the lock, allow other processing to happen for
374  // brief periods of time
375  //
376  // Give time for things to happen in other threads
377  QThread::yieldCurrentThread();
378 
380  qApp->sendPostedEvents(this, 0);
382 
383  if(p_stopping) return;
384  }
385  }
386  else {
387  while (!lockObject->tryLockForWrite()) {
388  // while we can't get the lock, allow other processing to happen for
389  // brief periods of time
390  //
391  // Give time for things to happen in other threads
392  QThread::yieldCurrentThread();
393 
395  qApp->sendPostedEvents(this, 0);
397 
398  if(p_stopping) return;
399  }
400  }
401  }
402 
421  void CubeDataThread::ReadCube(int cubeId, int startSample, int startLine,
422  int endSample, int endLine, int band,
423  void *caller) {
424 
425  if(!p_managedCubes->contains(cubeId)) {
426  IString msg = "cube ID [";
427  msg += IString(cubeId);
428  msg += "] is not a valid cube ID";
430  }
431 
432  GetCubeData(cubeId, startSample, startLine, endSample, endLine, band,
433  caller, true);
434  }
435 
456  void CubeDataThread::ReadWriteCube(int cubeId, int startSample,
457  int startLine, int endSample, int endLine,
458  int band, void *caller) {
459  if(!p_managedCubes->contains(cubeId)) {
460  IString msg = "cube ID [";
461  msg += IString(cubeId);
462  msg += "] is not a valid cube ID";
464  }
465 
466  GetCubeData(cubeId, startSample, startLine, endSample, endLine, band,
467  caller, false);
468  }
469 
483  int CubeDataThread::OverlapIndex(const Brick *overlapping, int cubeId,
484  int instanceNum, bool &exact) {
485  exact = false;
486 
487  // Start with extracting the range of the input (search) brick
488  int startSample = overlapping->Sample(0);
489  int endSample = overlapping->Sample(overlapping->size() - 1);
490  int startLine = overlapping->Line(0);
491  int endLine = overlapping->Line(overlapping->size() - 1);
492  int startBand = overlapping->Band(0);
493  int endBand = overlapping->Band(overlapping->size() - 1);
494 
495  // Now let's search for overlaps
496  ASSERT(p_managedData->size() == p_managedDataSources->size());
497  for (int knownBrick = 0; knownBrick < p_managedData->size(); knownBrick++) {
498  int sourceCube = (*p_managedDataSources)[knownBrick];
499 
500  // Ignore other cubes; they can't overlap
501  if (sourceCube != cubeId)
502  continue;
503 
504  QPair< QReadWriteLock *, Brick * > &managedBrick =
505  (*p_managedData)[knownBrick];
506 
507  Brick &brick = *managedBrick.second;
508 
509  // Get the range of this brick we've found in memory to see if any overlap
510  // exists
511  int compareSampStart = brick.Sample(0);
512  int compareSampEnd = brick.Sample(brick.size() - 1);
513  int compareLineStart = brick.Line(0);
514  int compareLineEnd = brick.Line(brick.size() - 1);
515  int compareBandStart = brick.Band(0);
516  int compareBandEnd = brick.Band(brick.size() - 1);
517 
518  bool overlap = false;
519 
520  // sample start is inside our sample range
521  if (compareSampStart >= startSample && compareSampStart <= endSample) {
522  overlap = true;
523  }
524 
525  // sample end is inside our sample range
526  if (compareSampEnd >= startSample && compareSampEnd <= endSample) {
527  overlap = true;
528  }
529 
530  // line start is in our line range
531  if (compareLineStart >= startLine && compareLineStart <= endLine) {
532  overlap = true;
533  }
534 
535  // line end is in our line range
536  if (compareLineEnd >= startLine && compareLineEnd <= endLine) {
537  overlap = true;
538  }
539 
540  // band start is in our line range
541  if (compareBandStart >= startBand && compareBandStart <= endBand) {
542  overlap = true;
543  }
544 
545  // band end is in our line range
546  if (compareBandEnd >= startBand && compareBandEnd <= endBand) {
547  overlap = true;
548  }
549 
550  exact = false;
551  if (compareSampStart == startSample && compareSampEnd == endSample
552  && compareLineStart == startLine && compareLineEnd == endLine
553  && compareBandStart == startBand && compareBandEnd == endBand) {
554  exact = true;
555  }
556 
557  // If we have overlap, and we're at the requested instance of overlap,
558  // return it.
559  if (overlap) {
560  instanceNum--;
561 
562  if (instanceNum < 0) {
563  return knownBrick;
564  }
565  }
566  }
567 
568  // None found at this instance
569  return -1;
570  }
571 
581  void CubeDataThread::DoneWithData(int cubeId, const Isis::Brick *brickDone) {
582  ASSERT(brickDone != NULL);
583  int instance = 0;
584  bool exactMatch = false;
585  bool writeLock = false;
586 
587  int index = OverlapIndex(brickDone, cubeId, instance, exactMatch);
588  ASSERT(p_managedData->size() == p_managedDataSources->size());
589  while (index != -1) {
590  // If this isn't the data they're finished with, we don't care about it
591  if (!exactMatch) {
592  instance++;
593  index = OverlapIndex(brickDone, cubeId, instance, exactMatch);
594  continue;
595  }
596 
597  // Test if we had a write lock (tryLockForRead will fail). If we had a
598  // write lock make note of it.
599  if (!(*p_managedData)[index].first->tryLockForRead()) {
600  if (writeLock) {
601  IString msg = "Overlapping data had write locks";
603  }
604 
605  writeLock = true;
606  }
607  // A read lock was in place, undo the lock we just made
608  else {
609  if (writeLock) {
610  IString msg = "Overlapping data had write locks";
612  }
613 
614  // Unlock the lock we just made
615  (*p_managedData)[index].first->unlock();
616  }
617 
618  // If we had a write lock we need to write the data to the file and
619  // notify others of the change if we have listeners.
620  if (writeLock) {
621  p_threadSafeMutex->lock();
622  Brick cpy(*brickDone);
623  p_managedCubes->value(cubeId).second->write(cpy);
624  p_threadSafeMutex->unlock();
625 
626  // Unlock the existing lock
627  (*p_managedData)[index].first->unlock();
628 
629  // No listeners? Remove this entry
630  if (p_numChangeListeners == 0) {
631  if (FreeBrick(index)) {
632  // We've freed the one and only match, nobody wants to know about
633  // it, so we're done
634  break;
635  }
636  }
637  // We have listeners, lock the data the appropriate number of times and
638  // then emit the BrickChanged with a pointer
639  else {
640  // notify others of this change
641  for (int i = 0; i < p_numChangeListeners; i++) {
642  AcquireLock((*p_managedData)[index].first, true);
643  }
644  emit BrickChanged((*p_managedDataSources)[index],
645  (*p_managedData)[index].second);
646  }
647  }
648  // if we had a read lock and no longer have any locks, remove data from
649  // list
650  else {
651  // We had the one and only (hopefully!) exact match, let's free it if
652  // we can get a write lock and be done.
653  // Free original read lock
654  (*p_managedData)[index].first->unlock();
655 
656  if ((*p_managedData)[index].first->tryLockForWrite()) {
657  (*p_managedData)[index].first->unlock();
658  FreeBrick(index);
659  }
660 
661  break;
662  }
663 
664  instance++;
665  index = OverlapIndex(brickDone, cubeId, instance, exactMatch);
666  }
667 
668  ASSERT(p_managedData->size() == p_managedDataSources->size());
669  }
670 
678  bool CubeDataThread::FreeBrick(int brickIndex) {
679  ASSERT(p_managedData->size() == p_managedDataSources->size());
680 
681  // make sure brick is not still being used!
682  if (!(*p_managedData)[brickIndex].first->tryLockForWrite()) {
684  "CubeDataThread::FreeBrick called on a locked brick",
685  _FILEINFO_);
686  }
687  else {
688  (*p_managedData)[brickIndex].first->unlock();
689  }
690 
691  // make sure no one is looking through p_managedData in order to delete it
692  p_threadSafeMutex->lock();
693 
694  if (p_currentLocksWaiting == 0) {
695  delete (*p_managedData)[brickIndex].first;
696  delete (*p_managedData)[brickIndex].second;
697 
698  p_managedData->removeAt(brickIndex);
699  p_managedDataSources->removeAt(brickIndex);
700 
701  // Try to free any leftover bricks too
702  for (int i = 0; i < p_managedData->size(); i++) {
703  if ((*p_managedData)[i].first->tryLockForWrite()) {
704  delete (*p_managedData)[i].first;
705  delete (*p_managedData)[i].second;
706  p_managedData->removeAt(i);
707  p_managedDataSources->removeAt(i);
708  i--;
709  }
710  }
711 
712  p_threadSafeMutex->unlock();
713  return true;
714  }
715 
716  p_threadSafeMutex->unlock();
717 
718  // no actual free was done
719  return false;
720  }
721 
729  p_threadSafeMutex->lock();
730  int numBricksInMemory = p_managedData->size();
731  p_threadSafeMutex->unlock();
732 
733  return numBricksInMemory;
734  }
735 
745  if (!p_managedCubes->contains(cubeId)) {
747  "Invalid Cube ID [" + IString(cubeId) + "]",
748  _FILEINFO_);
749  }
750 
751  return new UniversalGroundMap(*(*p_managedCubes)[cubeId].second);
752  }
753 
761  const Cube *CubeDataThread::GetCube(int cubeId) const {
762  if (!p_managedCubes->contains(cubeId)) {
764  "Invalid Cube ID [" + IString(cubeId) + "]",
765  _FILEINFO_);
766  }
767 
768  return (*p_managedCubes)[cubeId].second;
769  }
770 }
QMutex * p_threadSafeMutex
This locks the member variable p_managedCubes and p_managedData itself.
void GetCubeData(int cubeId, int ss, int sl, int es, int el, int band, void *caller, bool sharedLock)
This helper method reads in cube data and handles the locking of similar bricks appropriately.
void RemoveCube(int cubeId)
Removes a cube from this lock manager.
int Band(const int index=0) const
Returns the band position associated with a shape buffer index.
Definition: Buffer.cpp:178
int FindCubeId(const Cube *) const
Given a Cube pointer, return the cube ID associated with it.
File name manipulation and expansion.
Definition: FileName.h:116
Universal Ground Map.
void DoneWithData(int, const Isis::Brick *)
When done processing with a brick (reading or writing) this slot needs to be signalled to free locks ...
int p_numChangeListeners
This is the number of shaded locks to put on a brick when changes made.
unsigned int p_currentId
This is the unique id counter for cubes.
Buffer for containing a three dimensional section of an image.
Definition: Brick.h:61
void SetBasePosition(const int start_sample, const int start_line, const int start_band)
This method is used to set the base position of the shape buffer.
Definition: Brick.h:136
QList< QPair< QReadWriteLock *, Brick * > > * p_managedData
This is a list of bricks in memory and their locks.
QList< int > * p_managedDataSources
This is the associated cube ID with each brick.
This error is for when a programmer made an API call that was illegal.
Definition: IException.h:162
int size() const
Returns the total number of pixels in the shape buffer.
Definition: Buffer.h:113
void ReadReady(void *requester, int cubeId, const Isis::Brick *data)
This signal will be emitted when ReadCube has finished processing.
void AcquireLock(QReadWriteLock *lockObject, bool readLock)
This method is exclusively used to acquire locks.
int Sample(const int index=0) const
Returns the sample position associated with a shape buffer index.
Definition: Buffer.cpp:143
unsigned int p_currentLocksWaiting
Number of locks being attempted that re-entered the event loop.
virtual ~CubeDataThread()
This class is a self-contained thread, so normally it would be bad to simply delete it...
#define _FILEINFO_
Macro for the filename and line number.
Definition: IException.h:40
int AddCube(const FileName &fileName, bool mustOpenReadWrite=false)
This method is designed to be callable from any thread before data is requested, though no known side...
int Line(const int index=0) const
Returns the line position associated with a shape buffer index.
Definition: Buffer.cpp:161
QString expanded() const
Returns a QString of the full file name including the file path, excluding the attributes.
Definition: FileName.cpp:212
void open(const QString &cfile, QString access="r")
This method will open an isis cube for reading or reading/writing.
Definition: Cube.cpp:544
UniversalGroundMap * GetUniversalGroundMap(int cubeId) const
This returns a new Universal Ground Map given a Cube ID.
void AddChangeListener()
You must call this method after connecting to the BrickChanged signal, otherwise you are not guarante...
void ReadWriteReady(void *requester, int cubeId, Isis::Brick *data)
This signal will be emitted when ReadWriteCube has finished processing.
QMap< int, QPair< bool, Cube *> > * p_managedCubes
This is a list of the opened cubes.
void ReadCube(int cubeId, int startSample, int startLine, int endSample, int endLine, int band, void *caller)
This slot should be connected to and upon receiving a signal it will begin the necessary cube I/O to ...
Isis exception class.
Definition: IException.h:107
Adds specific functionality to C++ strings.
Definition: IString.h:181
Namespace for ISIS/Bullet specific routines.
Definition: Apollo.h:31
void RemoveChangeListener()
You must call this method after disconnecting from the BrickChanged signal, otherwise bricks cannot b...
void BrickChanged(int cubeId, const Isis::Brick *data)
DO NOT CONNECT TO THIS SIGNAL WITHOUT CALLING AddChangeListener().
void ReadWriteCube(int cubeId, int startSample, int startLine, int endSample, int endLine, int band, void *caller)
This slot should be connected to and upon receiving a signal it will begin the necessary cube I/O to ...
int OverlapIndex(const Brick *initial, int cubeId, int instanceNum, bool &exact)
This is a searching method used to identify overlapping data already in memory.
int BricksInMemory()
This is a helper method for both testing/debugging and general information that provides the current ...
bool p_stopping
This is set to help the shutdown process when deleted.
CubeDataThread()
This constructs a CubeDataThread().
const Cube * GetCube(int cubeId) const
This returns a constant pointer to a Cube at the given Cube ID.
IO Handler for Isis Cubes.
Definition: Cube.h:170
bool FreeBrick(int brickIndex)
This is used internally to delete bricks when possible.