Isis 3 Programmer Reference
CubeDataThread.cpp
1
6/* SPDX-License-Identifier: CC0-1.0 */
7#include "CubeDataThread.h"
8
9#include <QApplication>
10#include <QEventLoop>
11#include <QMap>
12#include <QMutex>
13#include <QPair>
14#include <QReadWriteLock>
15#include <QString>
16
17#include <iomanip>
18#include <iostream>
19
20#include "Brick.h"
21#include "Cube.h"
22#include "FileName.h"
23#include "IException.h"
24#include "IString.h"
25#include "UniversalGroundMap.h"
26
27namespace Isis {
35 p_managedCubes = NULL;
36 p_managedData = NULL;
37 p_threadSafeMutex = NULL;
39
40 p_managedCubes = new QMap< int, QPair< bool, Cube * > > ;
41 p_managedData = new QList< QPair< QReadWriteLock *, Brick * > > ;
42 p_threadSafeMutex = new QMutex();
44
47 p_currentId = 1; // start with ID == 1
48 p_stopping = false;
49
50 // Start this thread's event loop and make it so the slots are contained
51 // within this thread (this class automatically runs in its own thread)
52 start();
53 moveToThread(this);
54
55 }
56
57
66 // Shutdown the event loop(s)
67 QThread::exit(0);
68
69 p_stopping = true;
70
71 while (!isFinished()) {
72 QThread::yieldCurrentThread();
73 }
74
75 // Destroy the bricks still in memory
76 if (p_managedData) {
77 for (int i = p_managedData->size() - 1; i >= 0; i--) {
78 delete (*p_managedData)[i].first;
79 delete (*p_managedData)[i].second;
80 p_managedData->removeAt(i);
81 }
82
83 delete p_managedData;
84 p_managedData = NULL;
85 }
86
87 // Destroy the cubes still in memory
88 if (p_managedCubes) {
89 for (int i = p_managedCubes->size() - 1; i >= 0; i--) {
90 if ((p_managedCubes->end() - 1)->first) // only delete if we own it!
91 delete (p_managedCubes->end() - 1).value().second;
92 p_managedCubes->erase(p_managedCubes->end() - 1);
93 }
94 }
95
96 // Destroy the mutex that controls access to the cubes
98 delete p_threadSafeMutex;
99 p_threadSafeMutex = NULL;
100 }
101
102 // Destroy the data sources vector
106 }
107 }
108
125 bool mustOpenReadWrite) {
126 Cube *newCube = new Cube();
127
128 try {
129 newCube->open(fileName.expanded(), "rw");
130 }
131 catch (IException &e) {
132 if (!mustOpenReadWrite) {
133 newCube->open(fileName.expanded(), "r");
134 }
135 else {
136 throw;
137 }
138 }
139
140 p_threadSafeMutex->lock();
141
142 int newId = p_currentId;
143 p_currentId++;
144
145 QPair< bool, Cube * > newEntry;
146 newEntry.first = true; // we own this!
147 newEntry.second = newCube;
148 p_managedCubes->insert(newId, newEntry);
149
150 p_threadSafeMutex->unlock();
151
152 return newId;
153 }
154
168 p_threadSafeMutex->lock();
169
170 int newId = p_currentId;
171 p_currentId++;
172
173 QPair< bool, Cube * > newEntry;
174 newEntry.first = false; // we don't own this!
175 newEntry.second = cube;
176 p_managedCubes->insert(newId, newEntry);
177
178 p_threadSafeMutex->unlock();
179
180 return newId;
181 }
182
183
191 void CubeDataThread::RemoveCube(int cubeId) {
192 p_threadSafeMutex->lock();
193
194 QMap< int, QPair< bool, Cube * > >::iterator i;
195 i = p_managedCubes->find(cubeId);
196
197 if (i == p_managedCubes->end()) {
198 p_threadSafeMutex->unlock();
199 QString msg = "CubeDataThread::RemoveCube failed because cube ID [";
200 msg += QString::number(cubeId);
201 msg += "] not found";
202 throw IException(IException::Programmer, msg, _FILEINFO_);
203 }
204
205 if (p_managedDataSources->contains(cubeId)) {
206 p_threadSafeMutex->unlock();
207 QString msg = "CubeDataThread::RemoveCube failed cube ID [";
208 msg += QString::number(cubeId);
209 msg += "] has requested Bricks";
210 throw IException(IException::Programmer, msg, _FILEINFO_);
211 }
212
213 // if we have ownership of the cube then me must delete it
214 if (i.value().first)
215 delete i.value().second;
216 i.value().second = NULL;
217
218 p_managedCubes->remove(i.key());
219
220 p_threadSafeMutex->unlock();
221
222 }
223
224
232
240
257 void CubeDataThread::GetCubeData(int cubeId, int ss, int sl, int es, int el,
258 int band, void *caller, bool sharedLock) {
259
260 Brick *requestedBrick = NULL;
261
262 p_threadSafeMutex->lock();
263 requestedBrick = new Brick(*p_managedCubes->value(cubeId).second, es
264 - ss + 1, el - sl + 1, 1);
265 requestedBrick->SetBasePosition(ss, sl, band);
266 p_threadSafeMutex->unlock();
267
268 // See if we already have this brick
269 int instance = 0;
270 int exactIndex = -1;
271 bool exactMatch = false;
272 int index = OverlapIndex(requestedBrick, cubeId, instance, exactMatch);
273
274 // while overlaps are found
275 while (index != -1) {
276 if (sharedLock) {
277 // make sure we can get read locks on exact overlaps
278 // We need to try to get the lock to verify partial overlaps not
279 // write locked and only keep read locks on exact matches.
280 AcquireLock((*p_managedData)[index].first, true);
281 if (!exactMatch) {
282 (*p_managedData)[index].first->unlock();
283 }
284 }
285 else {
286 AcquireLock((*p_managedData)[index].first, false);
287 // we arent actually writing to this, but now we know we can delete it
288 (*p_managedData)[index].first->unlock();
289
290 exactMatch = false;
291
292 // destroy things that overlap but arent the same when asking to write
293 if (FreeBrick(index)) {
294 instance--;
295 }
296 }
297
298 if (exactMatch) {
299 exactIndex = index;
300 }
301
302 instance++;
303 index = OverlapIndex(requestedBrick, cubeId, instance, exactMatch);
304 }
305
306 if(p_stopping) return;
307
308 if (exactIndex == -1) {
309 p_threadSafeMutex->lock();
310
311 p_managedCubes->value(cubeId).second->read(*requestedBrick);
312
313 QPair< QReadWriteLock *, Brick * > managedDataEntry;
314
315 managedDataEntry.first = new QReadWriteLock();
316
317 AcquireLock(managedDataEntry.first, sharedLock);
318
319 managedDataEntry.second = requestedBrick;
320
321 p_managedData->append(managedDataEntry);
322 p_managedDataSources->append(cubeId);
323
324 exactIndex = p_managedData->size() - 1;
325
326 p_threadSafeMutex->unlock();
327 }
328
329 if (el - sl + 1 != (*p_managedData)[exactIndex].second->LineDimension())
330 {
331 //abort();
332 }
333
334 if (sharedLock) {
335 emit ReadReady(caller, cubeId, (*p_managedData)[exactIndex].second);
336 }
337 else {
338 emit ReadWriteReady(caller, cubeId, (*p_managedData)[exactIndex].second);
339 }
340 }
341
342
350 int CubeDataThread::FindCubeId(const Cube * cubeToFind) const {
351 QMapIterator< int, QPair< bool, Cube * > > i(*p_managedCubes);
352 while (i.hasNext()) {
353 i.next();
354 if (i.value().second == cubeToFind)
355 return i.key();
356 }
358 "Cube does not exist in this CubeDataThread", _FILEINFO_);
359 }
360
361
370 void CubeDataThread::AcquireLock(QReadWriteLock *lockObject, bool readLock) {
371 // This method can be called recursively (sorta). In the "recursive"
372 // cases p_currentLockWaiting is > 0. This guarantees that locks are
373 // aquired in the reverse order as they are requested, which isn't
374 // particularly helpful, but it is consistent :)
375
376 if (readLock) {
377 while (!lockObject->tryLockForRead()) {
378 // while we can't get the lock, allow other processing to happen for
379 // brief periods of time
380 //
381 // Give time for things to happen in other threads
382 QThread::yieldCurrentThread();
383
385 qApp->sendPostedEvents(this, 0);
387
388 if(p_stopping) return;
389 }
390 }
391 else {
392 while (!lockObject->tryLockForWrite()) {
393 // while we can't get the lock, allow other processing to happen for
394 // brief periods of time
395 //
396 // Give time for things to happen in other threads
397 QThread::yieldCurrentThread();
398
400 qApp->sendPostedEvents(this, 0);
402
403 if(p_stopping) return;
404 }
405 }
406 }
407
426 void CubeDataThread::ReadCube(int cubeId, int startSample, int startLine,
427 int endSample, int endLine, int band,
428 void *caller) {
429
430 if(!p_managedCubes->contains(cubeId)) {
431 IString msg = "cube ID [";
432 msg += IString(cubeId);
433 msg += "] is not a valid cube ID";
434 throw IException(IException::Programmer, msg, _FILEINFO_);
435 }
436
437 GetCubeData(cubeId, startSample, startLine, endSample, endLine, band,
438 caller, true);
439 }
440
461 void CubeDataThread::ReadWriteCube(int cubeId, int startSample,
462 int startLine, int endSample, int endLine,
463 int band, void *caller) {
464 if(!p_managedCubes->contains(cubeId)) {
465 IString msg = "cube ID [";
466 msg += IString(cubeId);
467 msg += "] is not a valid cube ID";
468 throw IException(IException::Programmer, msg, _FILEINFO_);
469 }
470
471 GetCubeData(cubeId, startSample, startLine, endSample, endLine, band,
472 caller, false);
473 }
474
488 int CubeDataThread::OverlapIndex(const Brick *overlapping, int cubeId,
489 int instanceNum, bool &exact) {
490 exact = false;
491
492 // Start with extracting the range of the input (search) brick
493 int startSample = overlapping->Sample(0);
494 int endSample = overlapping->Sample(overlapping->size() - 1);
495 int startLine = overlapping->Line(0);
496 int endLine = overlapping->Line(overlapping->size() - 1);
497 int startBand = overlapping->Band(0);
498 int endBand = overlapping->Band(overlapping->size() - 1);
499
500 // Now let's search for overlaps
501 for (int knownBrick = 0; knownBrick < p_managedData->size(); knownBrick++) {
502 int sourceCube = (*p_managedDataSources)[knownBrick];
503
504 // Ignore other cubes; they can't overlap
505 if (sourceCube != cubeId)
506 continue;
507
508 QPair< QReadWriteLock *, Brick * > &managedBrick =
509 (*p_managedData)[knownBrick];
510
511 Brick &brick = *managedBrick.second;
512
513 // Get the range of this brick we've found in memory to see if any overlap
514 // exists
515 int compareSampStart = brick.Sample(0);
516 int compareSampEnd = brick.Sample(brick.size() - 1);
517 int compareLineStart = brick.Line(0);
518 int compareLineEnd = brick.Line(brick.size() - 1);
519 int compareBandStart = brick.Band(0);
520 int compareBandEnd = brick.Band(brick.size() - 1);
521
522 bool overlap = false;
523
524 // sample start is inside our sample range
525 if (compareSampStart >= startSample && compareSampStart <= endSample) {
526 overlap = true;
527 }
528
529 // sample end is inside our sample range
530 if (compareSampEnd >= startSample && compareSampEnd <= endSample) {
531 overlap = true;
532 }
533
534 // line start is in our line range
535 if (compareLineStart >= startLine && compareLineStart <= endLine) {
536 overlap = true;
537 }
538
539 // line end is in our line range
540 if (compareLineEnd >= startLine && compareLineEnd <= endLine) {
541 overlap = true;
542 }
543
544 // band start is in our line range
545 if (compareBandStart >= startBand && compareBandStart <= endBand) {
546 overlap = true;
547 }
548
549 // band end is in our line range
550 if (compareBandEnd >= startBand && compareBandEnd <= endBand) {
551 overlap = true;
552 }
553
554 exact = false;
555 if (compareSampStart == startSample && compareSampEnd == endSample
556 && compareLineStart == startLine && compareLineEnd == endLine
557 && compareBandStart == startBand && compareBandEnd == endBand) {
558 exact = true;
559 }
560
561 // If we have overlap, and we're at the requested instance of overlap,
562 // return it.
563 if (overlap) {
564 instanceNum--;
565
566 if (instanceNum < 0) {
567 return knownBrick;
568 }
569 }
570 }
571
572 // None found at this instance
573 return -1;
574 }
575
585 void CubeDataThread::DoneWithData(int cubeId, const Isis::Brick *brickDone) {
586 int instance = 0;
587 bool exactMatch = false;
588 bool writeLock = false;
589
590 int index = OverlapIndex(brickDone, cubeId, instance, exactMatch);
591 while (index != -1) {
592 // If this isn't the data they're finished with, we don't care about it
593 if (!exactMatch) {
594 instance++;
595 index = OverlapIndex(brickDone, cubeId, instance, exactMatch);
596 continue;
597 }
598
599 // Test if we had a write lock (tryLockForRead will fail). If we had a
600 // write lock make note of it.
601 if (!(*p_managedData)[index].first->tryLockForRead()) {
602 if (writeLock) {
603 IString msg = "Overlapping data had write locks";
604 throw IException(IException::Programmer, msg, _FILEINFO_);
605 }
606
607 writeLock = true;
608 }
609 // A read lock was in place, undo the lock we just made
610 else {
611 if (writeLock) {
612 IString msg = "Overlapping data had write locks";
613 throw IException(IException::Programmer, msg, _FILEINFO_);
614 }
615
616 // Unlock the lock we just made
617 (*p_managedData)[index].first->unlock();
618 }
619
620 // If we had a write lock we need to write the data to the file and
621 // notify others of the change if we have listeners.
622 if (writeLock) {
623 p_threadSafeMutex->lock();
624 Brick cpy(*brickDone);
625 p_managedCubes->value(cubeId).second->write(cpy);
626 p_threadSafeMutex->unlock();
627
628 // Unlock the existing lock
629 (*p_managedData)[index].first->unlock();
630
631 // No listeners? Remove this entry
632 if (p_numChangeListeners == 0) {
633 if (FreeBrick(index)) {
634 // We've freed the one and only match, nobody wants to know about
635 // it, so we're done
636 break;
637 }
638 }
639 // We have listeners, lock the data the appropriate number of times and
640 // then emit the BrickChanged with a pointer
641 else {
642 // notify others of this change
643 for (int i = 0; i < p_numChangeListeners; i++) {
644 AcquireLock((*p_managedData)[index].first, true);
645 }
646 emit BrickChanged((*p_managedDataSources)[index],
647 (*p_managedData)[index].second);
648 }
649 }
650 // if we had a read lock and no longer have any locks, remove data from
651 // list
652 else {
653 // We had the one and only (hopefully!) exact match, let's free it if
654 // we can get a write lock and be done.
655 // Free original read lock
656 (*p_managedData)[index].first->unlock();
657
658 if ((*p_managedData)[index].first->tryLockForWrite()) {
659 (*p_managedData)[index].first->unlock();
660 FreeBrick(index);
661 }
662
663 break;
664 }
665
666 instance++;
667 index = OverlapIndex(brickDone, cubeId, instance, exactMatch);
668 }
669
670 }
671
679 bool CubeDataThread::FreeBrick(int brickIndex) {
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}
Buffer for containing a three dimensional section of an image.
Definition Brick.h:45
int Sample(const int index=0) const
Returns the sample position associated with a shape buffer index.
Definition Buffer.cpp:127
CubeDataThread()
This constructs a CubeDataThread().
int BricksInMemory()
This is a helper method for both testing/debugging and general information that provides the current ...
int p_numChangeListeners
This is the number of shaded locks to put on a brick when changes made.
void BrickChanged(int cubeId, const Isis::Brick *data)
DO NOT CONNECT TO THIS SIGNAL WITHOUT CALLING AddChangeListener().
void RemoveChangeListener()
You must call this method after disconnecting from the BrickChanged signal, otherwise bricks cannot b...
QMutex * p_threadSafeMutex
This locks the member variable p_managedCubes and p_managedData itself.
UniversalGroundMap * GetUniversalGroundMap(int cubeId) const
This returns a new Universal Ground Map given a Cube ID.
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...
unsigned int p_currentId
This is the unique id counter for cubes.
bool p_stopping
This is set to help the shutdown process when deleted.
virtual ~CubeDataThread()
This class is a self-contained thread, so normally it would be bad to simply delete it.
int FindCubeId(const Cube *) const
Given a Cube pointer, return the cube ID associated with it.
int OverlapIndex(const Brick *initial, int cubeId, int instanceNum, bool &exact)
This is a searching method used to identify overlapping data already in memory.
void AddChangeListener()
You must call this method after connecting to the BrickChanged signal, otherwise you are not guarante...
void AcquireLock(QReadWriteLock *lockObject, bool readLock)
This method is exclusively used to acquire locks.
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 ...
const Cube * GetCube(int cubeId) const
This returns a constant pointer to a Cube at the given Cube ID.
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 ...
void RemoveCube(int cubeId)
Removes a cube from this lock manager.
QMap< int, QPair< bool, Cube * > > * p_managedCubes
This is a list of the opened cubes.
unsigned int p_currentLocksWaiting
Number of locks being attempted that re-entered the event loop.
void ReadReady(void *requester, int cubeId, const Isis::Brick *data)
This signal will be emitted when ReadCube has finished processing.
QList< QPair< QReadWriteLock *, Brick * > > * p_managedData
This is a list of bricks in memory and their locks.
void DoneWithData(int, const Isis::Brick *)
When done processing with a brick (reading or writing) this slot needs to be signalled to free locks ...
QList< int > * p_managedDataSources
This is the associated cube ID with each brick.
bool FreeBrick(int brickIndex)
This is used internally to delete bricks when possible.
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 ReadWriteReady(void *requester, int cubeId, Isis::Brick *data)
This signal will be emitted when ReadWriteCube has finished processing.
IO Handler for Isis Cubes.
Definition Cube.h:168
File name manipulation and expansion.
Definition FileName.h:100
QString expanded() const
Returns a QString of the full file name including the file path, excluding the attributes.
Definition FileName.cpp:196
Isis exception class.
Definition IException.h:91
@ Programmer
This error is for when a programmer made an API call that was illegal.
Definition IException.h:146
Adds specific functionality to C++ strings.
Definition IString.h:165
Universal Ground Map.
This is free and unencumbered software released into the public domain.
Definition Apollo.h:16