Commit 0d7cfeeff9b9405d488ec351a2758910b51c682f
Merge pull request #79 from biometrics/distribute_priority
A way to avoid deadlocks when using stream inside a distribute transform
Showing
3 changed files
with
242 additions
and
135 deletions
openbr/core/resource.h
| ... | ... | @@ -24,6 +24,7 @@ |
| 24 | 24 | #include <QSharedPointer> |
| 25 | 25 | #include <QString> |
| 26 | 26 | #include <QThread> |
| 27 | +#include <openbr/openbr_plugin.h> | |
| 27 | 28 | |
| 28 | 29 | template <typename T> |
| 29 | 30 | class ResourceMaker |
| ... | ... | @@ -52,7 +53,7 @@ public: |
| 52 | 53 | : resourceMaker(rm) |
| 53 | 54 | , availableResources(new QList<T*>()) |
| 54 | 55 | , lock(new QMutex()) |
| 55 | - , totalResources(new QSemaphore(QThread::idealThreadCount())) | |
| 56 | + , totalResources(new QSemaphore(br::Globals->parallelism)) | |
| 56 | 57 | {} |
| 57 | 58 | |
| 58 | 59 | ~Resource() | ... | ... |
openbr/plugins/meta.cpp
| ... | ... | @@ -645,17 +645,28 @@ public: |
| 645 | 645 | QList<TemplateList> input_buffer; |
| 646 | 646 | input_buffer.reserve(src.size()); |
| 647 | 647 | |
| 648 | + QFutureSynchronizer<void> futures; | |
| 649 | + | |
| 648 | 650 | for (int i =0; i < src.size();i++) { |
| 649 | 651 | input_buffer.append(TemplateList()); |
| 650 | 652 | output_buffer.append(TemplateList()); |
| 651 | 653 | } |
| 652 | - | |
| 653 | - QFutureSynchronizer<void> futures; | |
| 654 | + QList<QFuture<void> > temp; | |
| 655 | + temp.reserve(src.size()); | |
| 654 | 656 | for (int i=0; i<src.size(); i++) { |
| 655 | 657 | input_buffer[i].append(src[i]); |
| 656 | - if (Globals->parallelism) futures.addFuture(QtConcurrent::run(_projectList, transform, &input_buffer[i], &output_buffer[i])); | |
| 657 | - else _projectList( transform, &input_buffer[i], &output_buffer[i]); | |
| 658 | + | |
| 659 | + if (Globals->parallelism > 1) temp.append(QtConcurrent::run(_projectList, transform, &input_buffer[i], &output_buffer[i])); | |
| 660 | + else _projectList(transform, &input_buffer[i], &output_buffer[i]); | |
| 661 | + } | |
| 662 | + // We add the futures in reverse order, since in Qt 5.1 at least the | |
| 663 | + // waiting thread will wait on them in the order added (which for uniform priority | |
| 664 | + // threads is the order of execution), and we want the waiting thread to go in the opposite order | |
| 665 | + // so that it can steal runnables and do something besides wait. | |
| 666 | + for (int i = temp.size() - 1; i > 0; i--) { | |
| 667 | + futures.addFuture(temp[i]); | |
| 658 | 668 | } |
| 669 | + | |
| 659 | 670 | futures.waitForFinished(); |
| 660 | 671 | |
| 661 | 672 | for (int i=0; i<src.size(); i++) dst.append(output_buffer[i]); | ... | ... |
openbr/plugins/stream.cpp
| ... | ... | @@ -160,9 +160,8 @@ class DataSource |
| 160 | 160 | public: |
| 161 | 161 | DataSource(int maxFrames=500) |
| 162 | 162 | { |
| 163 | + // The sequence number of the last frame | |
| 163 | 164 | final_frame = -1; |
| 164 | - last_issued = -2; | |
| 165 | - last_received = -3; | |
| 166 | 165 | for (int i=0; i < maxFrames;i++) |
| 167 | 166 | { |
| 168 | 167 | allFrames.addItem(new FrameData()); |
| ... | ... | @@ -181,51 +180,64 @@ public: |
| 181 | 180 | } |
| 182 | 181 | |
| 183 | 182 | // non-blocking version of getFrame |
| 184 | - FrameData * tryGetFrame() | |
| 183 | + // Returns a NULL FrameData if too many frames are out, or the | |
| 184 | + // data source is broken. Sets last_frame to true iff the FrameData | |
| 185 | + // returned is the last valid frame, and the data source is now broken. | |
| 186 | + FrameData * tryGetFrame(bool & last_frame) | |
| 185 | 187 | { |
| 188 | + last_frame = false; | |
| 189 | + | |
| 190 | + if (is_broken) { | |
| 191 | + return NULL; | |
| 192 | + } | |
| 193 | + | |
| 194 | + // Try to get a FrameData from the pool, if we can't it means too many | |
| 195 | + // frames are already out, and we will return NULL to indicate failure | |
| 186 | 196 | FrameData * aFrame = allFrames.tryGetItem(); |
| 187 | - if (aFrame == NULL) | |
| 197 | + if (aFrame == NULL) { | |
| 188 | 198 | return NULL; |
| 199 | + } | |
| 189 | 200 | |
| 190 | 201 | aFrame->data.clear(); |
| 191 | 202 | aFrame->sequenceNumber = -1; |
| 192 | 203 | |
| 204 | + // Try to read a frame, if this returns false the data source is broken | |
| 193 | 205 | bool res = getNext(*aFrame); |
| 194 | 206 | |
| 195 | - // The datasource broke. | |
| 196 | - if (!res) { | |
| 207 | + // The datasource broke, update final_frame | |
| 208 | + if (!res) | |
| 209 | + { | |
| 210 | + QMutexLocker lock(&last_frame_update); | |
| 211 | + final_frame = lookAhead.back()->sequenceNumber; | |
| 197 | 212 | allFrames.addItem(aFrame); |
| 213 | + } | |
| 214 | + else lookAhead.push_back(aFrame); | |
| 198 | 215 | |
| 199 | - QMutexLocker lock(&last_frame_update); | |
| 200 | - // Did we already receive the last frame? | |
| 201 | - final_frame = last_issued; | |
| 216 | + FrameData * rVal = lookAhead.first(); | |
| 217 | + lookAhead.pop_front(); | |
| 202 | 218 | |
| 203 | - // We got the last frame before the data source broke, | |
| 204 | - // better pulse lastReturned | |
| 205 | - if (final_frame == last_received) { | |
| 206 | - lastReturned.wakeAll(); | |
| 207 | - } | |
| 208 | - else if (final_frame < last_received) | |
| 209 | - std::cout << "Bad last frame " << final_frame << " but received " << last_received << std::endl; | |
| 210 | 219 | |
| 211 | - return NULL; | |
| 220 | + if (rVal->sequenceNumber == final_frame) { | |
| 221 | + last_frame = true; | |
| 222 | + is_broken = true; | |
| 212 | 223 | } |
| 213 | - last_issued = aFrame->sequenceNumber; | |
| 214 | - return aFrame; | |
| 224 | + | |
| 225 | + return rVal; | |
| 215 | 226 | } |
| 216 | 227 | |
| 217 | - // Returns true if the frame returned was the last | |
| 228 | + // Return a frame to the pool, returns true if the frame returned was the last | |
| 218 | 229 | // frame issued, false otherwise |
| 219 | 230 | bool returnFrame(FrameData * inputFrame) |
| 220 | 231 | { |
| 232 | + int frameNumber = inputFrame->sequenceNumber; | |
| 233 | + | |
| 221 | 234 | allFrames.addItem(inputFrame); |
| 222 | 235 | |
| 223 | 236 | bool rval = false; |
| 224 | 237 | |
| 225 | 238 | QMutexLocker lock(&last_frame_update); |
| 226 | - last_received = inputFrame->sequenceNumber; | |
| 227 | 239 | |
| 228 | - if (inputFrame->sequenceNumber == final_frame) { | |
| 240 | + if (frameNumber == final_frame) { | |
| 229 | 241 | // We just received the last frame, better pulse |
| 230 | 242 | lastReturned.wakeAll(); |
| 231 | 243 | rval = true; |
| ... | ... | @@ -240,17 +252,57 @@ public: |
| 240 | 252 | lastReturned.wait(&last_frame_update); |
| 241 | 253 | } |
| 242 | 254 | |
| 243 | - virtual void close() = 0; | |
| 244 | - virtual bool open(Template & output, int start_index=0) = 0; | |
| 245 | - virtual bool isOpen() = 0; | |
| 255 | + bool open(Template & output, int start_index = 0) | |
| 256 | + { | |
| 257 | + is_broken = false; | |
| 258 | + // The last frame isn't initialized yet | |
| 259 | + final_frame = -1; | |
| 260 | + // Start our sequence numbers from the input index | |
| 261 | + next_sequence_number = start_index; | |
| 262 | + | |
| 263 | + // Actually open the data source | |
| 264 | + bool open_res = concreteOpen(output); | |
| 265 | + | |
| 266 | + // We couldn't open the data source | |
| 267 | + if (!open_res) { | |
| 268 | + is_broken = true; | |
| 269 | + return false; | |
| 270 | + } | |
| 271 | + | |
| 272 | + // Try to get a frame from the global pool | |
| 273 | + FrameData * firstFrame = allFrames.tryGetItem(); | |
| 274 | + | |
| 275 | + // If this fails, things have gone pretty badly. | |
| 276 | + if (firstFrame == NULL) { | |
| 277 | + is_broken = true; | |
| 278 | + return false; | |
| 279 | + } | |
| 280 | + | |
| 281 | + // Read a frame from the video source | |
| 282 | + bool res = getNext(*firstFrame); | |
| 283 | + | |
| 284 | + // the data source broke already, we couldn't even get one frame | |
| 285 | + // from it. | |
| 286 | + if (!res) { | |
| 287 | + is_broken = true; | |
| 288 | + return false; | |
| 289 | + } | |
| 246 | 290 | |
| 291 | + lookAhead.append(firstFrame); | |
| 292 | + return true; | |
| 293 | + } | |
| 294 | + | |
| 295 | + virtual bool isOpen()=0; | |
| 296 | + virtual bool concreteOpen(Template & output) = 0; | |
| 247 | 297 | virtual bool getNext(FrameData & input) = 0; |
| 298 | + virtual void close() = 0; | |
| 248 | 299 | |
| 300 | + int next_sequence_number; | |
| 249 | 301 | protected: |
| 250 | 302 | DoubleBuffer allFrames; |
| 251 | 303 | int final_frame; |
| 252 | - int last_issued; | |
| 253 | - int last_received; | |
| 304 | + bool is_broken; | |
| 305 | + QList<FrameData *> lookAhead; | |
| 254 | 306 | |
| 255 | 307 | QWaitCondition lastReturned; |
| 256 | 308 | QMutex last_frame_update; |
| ... | ... | @@ -262,13 +314,8 @@ class VideoDataSource : public DataSource |
| 262 | 314 | public: |
| 263 | 315 | VideoDataSource(int maxFrames) : DataSource(maxFrames) {} |
| 264 | 316 | |
| 265 | - bool open(Template &input, int start_index=0) | |
| 317 | + bool concreteOpen(Template &input) | |
| 266 | 318 | { |
| 267 | - final_frame = -1; | |
| 268 | - last_issued = -2; | |
| 269 | - last_received = -3; | |
| 270 | - | |
| 271 | - next_idx = start_index; | |
| 272 | 319 | basis = input; |
| 273 | 320 | bool is_int = false; |
| 274 | 321 | int anInt = input.file.name.toInt(&is_int); |
| ... | ... | @@ -306,25 +353,31 @@ private: |
| 306 | 353 | return false; |
| 307 | 354 | |
| 308 | 355 | output.data.append(Template(basis.file)); |
| 309 | - output.data.last().append(cv::Mat()); | |
| 356 | + output.data.last().m() = cv::Mat(); | |
| 310 | 357 | |
| 311 | - output.sequenceNumber = next_idx; | |
| 312 | - next_idx++; | |
| 358 | + output.sequenceNumber = next_sequence_number; | |
| 359 | + next_sequence_number++; | |
| 313 | 360 | |
| 314 | - bool res = video.read(output.data.last().last()); | |
| 315 | - output.data.last().last() = output.data.last().last().clone(); | |
| 361 | + cv::Mat temp; | |
| 362 | + bool res = video.read(temp); | |
| 316 | 363 | |
| 317 | 364 | if (!res) { |
| 365 | + output.data.last().m() = cv::Mat(); | |
| 318 | 366 | close(); |
| 319 | 367 | return false; |
| 320 | 368 | } |
| 369 | + | |
| 370 | + // This clone is critical, if we don't do it then the matrix will | |
| 371 | + // be an alias of an internal buffer of the video source, leading | |
| 372 | + // to various problems later. | |
| 373 | + output.data.last().m() = temp.clone(); | |
| 374 | + | |
| 321 | 375 | output.data.last().file.set("FrameNumber", output.sequenceNumber); |
| 322 | 376 | return true; |
| 323 | 377 | } |
| 324 | 378 | |
| 325 | 379 | cv::VideoCapture video; |
| 326 | 380 | Template basis; |
| 327 | - int next_idx; | |
| 328 | 381 | }; |
| 329 | 382 | |
| 330 | 383 | // Given a template as input, return its matrices one by one on subsequent calls |
| ... | ... | @@ -334,21 +387,16 @@ class TemplateDataSource : public DataSource |
| 334 | 387 | public: |
| 335 | 388 | TemplateDataSource(int maxFrames) : DataSource(maxFrames) |
| 336 | 389 | { |
| 337 | - current_idx = INT_MAX; | |
| 390 | + current_matrix_idx = INT_MAX; | |
| 338 | 391 | data_ok = false; |
| 339 | 392 | } |
| 340 | - bool data_ok; | |
| 341 | 393 | |
| 342 | - bool open(Template &input, int start_index=0) | |
| 394 | + bool concreteOpen(Template &input) | |
| 343 | 395 | { |
| 344 | 396 | basis = input; |
| 345 | - current_idx = 0; | |
| 346 | - next_sequence = start_index; | |
| 347 | - final_frame = -1; | |
| 348 | - last_issued = -2; | |
| 349 | - last_received = -3; | |
| 397 | + current_matrix_idx = 0; | |
| 350 | 398 | |
| 351 | - data_ok = current_idx < basis.size(); | |
| 399 | + data_ok = current_matrix_idx < basis.size(); | |
| 352 | 400 | return data_ok; |
| 353 | 401 | } |
| 354 | 402 | |
| ... | ... | @@ -358,39 +406,41 @@ public: |
| 358 | 406 | |
| 359 | 407 | void close() |
| 360 | 408 | { |
| 361 | - current_idx = INT_MAX; | |
| 409 | + current_matrix_idx = INT_MAX; | |
| 362 | 410 | basis.clear(); |
| 363 | 411 | } |
| 364 | 412 | |
| 365 | 413 | private: |
| 366 | 414 | bool getNext(FrameData & output) |
| 367 | 415 | { |
| 368 | - data_ok = current_idx < basis.size(); | |
| 416 | + data_ok = current_matrix_idx < basis.size(); | |
| 369 | 417 | if (!data_ok) |
| 370 | 418 | return false; |
| 371 | 419 | |
| 372 | - output.data.append(basis[current_idx]); | |
| 373 | - current_idx++; | |
| 420 | + output.data.append(basis[current_matrix_idx]); | |
| 421 | + current_matrix_idx++; | |
| 374 | 422 | |
| 375 | - output.sequenceNumber = next_sequence; | |
| 376 | - next_sequence++; | |
| 423 | + output.sequenceNumber = next_sequence_number; | |
| 424 | + next_sequence_number++; | |
| 377 | 425 | |
| 378 | 426 | output.data.last().file.set("FrameNumber", output.sequenceNumber); |
| 379 | 427 | return true; |
| 380 | 428 | } |
| 381 | 429 | |
| 382 | 430 | Template basis; |
| 383 | - int current_idx; | |
| 384 | - int next_sequence; | |
| 431 | + // Index of the next matrix to output from the template | |
| 432 | + int current_matrix_idx; | |
| 433 | + | |
| 434 | + // is current_matrix_idx in bounds? | |
| 435 | + bool data_ok; | |
| 385 | 436 | }; |
| 386 | 437 | |
| 387 | -// Given a template as input, create a VideoDataSource or a TemplateDataSource | |
| 388 | -// depending on whether or not it looks like the input template has already | |
| 389 | -// loaded frames into memory. | |
| 438 | +// Given a templatelist as input, create appropriate data source for each | |
| 439 | +// individual template | |
| 390 | 440 | class DataSourceManager : public DataSource |
| 391 | 441 | { |
| 392 | 442 | public: |
| 393 | - DataSourceManager() | |
| 443 | + DataSourceManager() : DataSource(500) | |
| 394 | 444 | { |
| 395 | 445 | actualSource = NULL; |
| 396 | 446 | } |
| ... | ... | @@ -411,29 +461,25 @@ public: |
| 411 | 461 | |
| 412 | 462 | bool open(TemplateList & input) |
| 413 | 463 | { |
| 414 | - currentIdx = 0; | |
| 464 | + current_template_idx = 0; | |
| 415 | 465 | templates = input; |
| 416 | 466 | |
| 417 | - return open(templates[currentIdx]); | |
| 467 | + return DataSource::open(templates[current_template_idx]); | |
| 418 | 468 | } |
| 419 | 469 | |
| 420 | - bool open(Template & input, int start_index=0) | |
| 470 | + bool concreteOpen(Template & input) | |
| 421 | 471 | { |
| 422 | 472 | close(); |
| 423 | - final_frame = -1; | |
| 424 | - last_issued = -2; | |
| 425 | - last_received = -3; | |
| 426 | - next_frame = start_index; | |
| 427 | 473 | |
| 428 | 474 | // Input has no matrices? Its probably a video that hasn't been loaded yet |
| 429 | 475 | if (input.empty()) { |
| 430 | 476 | actualSource = new VideoDataSource(0); |
| 431 | - actualSource->open(input, next_frame); | |
| 477 | + actualSource->concreteOpen(input); | |
| 432 | 478 | } |
| 433 | 479 | else { |
| 434 | 480 | // create frame dealer |
| 435 | 481 | actualSource = new TemplateDataSource(0); |
| 436 | - actualSource->open(input, next_frame); | |
| 482 | + actualSource->concreteOpen(input); | |
| 437 | 483 | } |
| 438 | 484 | if (!isOpen()) { |
| 439 | 485 | delete actualSource; |
| ... | ... | @@ -446,30 +492,47 @@ public: |
| 446 | 492 | bool isOpen() { return !actualSource ? false : actualSource->isOpen(); } |
| 447 | 493 | |
| 448 | 494 | protected: |
| 449 | - int currentIdx; | |
| 450 | - int next_frame; | |
| 495 | + // Index of the template in the templatelist we are currently reading from | |
| 496 | + int current_template_idx; | |
| 497 | + | |
| 451 | 498 | TemplateList templates; |
| 452 | 499 | DataSource * actualSource; |
| 453 | 500 | bool getNext(FrameData & output) |
| 454 | 501 | { |
| 455 | 502 | bool res = actualSource->getNext(output); |
| 503 | + output.sequenceNumber = next_sequence_number; | |
| 504 | + | |
| 456 | 505 | if (res) { |
| 457 | - next_frame = output.sequenceNumber+1; | |
| 506 | + output.data.last().file.set("FrameNumber", output.sequenceNumber); | |
| 507 | + next_sequence_number++; | |
| 508 | + if (output.data.last().last().empty()) | |
| 509 | + qDebug("broken matrix"); | |
| 458 | 510 | return true; |
| 459 | 511 | } |
| 460 | 512 | |
| 513 | + | |
| 461 | 514 | while(!res) { |
| 462 | - currentIdx++; | |
| 515 | + output.data.clear(); | |
| 516 | + current_template_idx++; | |
| 463 | 517 | |
| 464 | - if (currentIdx >= templates.size()) | |
| 518 | + // No more templates? We're done | |
| 519 | + if (current_template_idx >= templates.size()) | |
| 465 | 520 | return false; |
| 466 | - bool open_res = open(templates[currentIdx], next_frame); | |
| 521 | + | |
| 522 | + // open the next data source | |
| 523 | + bool open_res = concreteOpen(templates[current_template_idx]); | |
| 467 | 524 | if (!open_res) |
| 468 | 525 | return false; |
| 526 | + | |
| 527 | + // get a frame from it | |
| 469 | 528 | res = actualSource->getNext(output); |
| 470 | 529 | } |
| 530 | + output.sequenceNumber = next_sequence_number++; | |
| 531 | + output.data.last().file.set("FrameNumber", output.sequenceNumber); | |
| 532 | + | |
| 533 | + if (output.data.last().last().empty()) | |
| 534 | + qDebug("broken matrix"); | |
| 471 | 535 | |
| 472 | - next_frame = output.sequenceNumber+1; | |
| 473 | 536 | return res; |
| 474 | 537 | } |
| 475 | 538 | |
| ... | ... | @@ -477,9 +540,14 @@ protected: |
| 477 | 540 | |
| 478 | 541 | class ProcessingStage; |
| 479 | 542 | |
| 480 | -class BasicLoop : public QRunnable | |
| 543 | +class BasicLoop : public QRunnable, public QFutureInterface<void> | |
| 481 | 544 | { |
| 482 | 545 | public: |
| 546 | + BasicLoop() | |
| 547 | + { | |
| 548 | + this->reportStarted(); | |
| 549 | + } | |
| 550 | + | |
| 483 | 551 | void run(); |
| 484 | 552 | |
| 485 | 553 | QList<ProcessingStage *> * stages; |
| ... | ... | @@ -505,13 +573,13 @@ public: |
| 505 | 573 | int stage_id; |
| 506 | 574 | |
| 507 | 575 | virtual void reset()=0; |
| 508 | - | |
| 509 | 576 | protected: |
| 510 | 577 | int thread_count; |
| 511 | 578 | |
| 512 | 579 | SharedBuffer * inputBuffer; |
| 513 | 580 | ProcessingStage * nextStage; |
| 514 | 581 | QList<ProcessingStage *> * stages; |
| 582 | + QThreadPool * threads; | |
| 515 | 583 | Transform * transform; |
| 516 | 584 | |
| 517 | 585 | }; |
| ... | ... | @@ -530,6 +598,7 @@ void BasicLoop::run() |
| 530 | 598 | current_idx++; |
| 531 | 599 | current_idx = current_idx % stages->size(); |
| 532 | 600 | } |
| 601 | + this->reportFinished(); | |
| 533 | 602 | } |
| 534 | 603 | |
| 535 | 604 | class MultiThreadStage : public ProcessingStage |
| ... | ... | @@ -564,7 +633,6 @@ public: |
| 564 | 633 | } |
| 565 | 634 | }; |
| 566 | 635 | |
| 567 | - | |
| 568 | 636 | class SingleThreadStage : public ProcessingStage |
| 569 | 637 | { |
| 570 | 638 | public: |
| ... | ... | @@ -627,18 +695,20 @@ public: |
| 627 | 695 | lock.unlock(); |
| 628 | 696 | |
| 629 | 697 | if (newItem) |
| 630 | - { | |
| 631 | - BasicLoop * next = new BasicLoop(); | |
| 632 | - next->stages = stages; | |
| 633 | - next->start_idx = this->stage_id; | |
| 634 | - next->startItem = newItem; | |
| 635 | - | |
| 636 | - QThreadPool::globalInstance()->start(next, stages->size() - this->stage_id); | |
| 637 | - } | |
| 698 | + startThread(newItem); | |
| 638 | 699 | |
| 639 | 700 | return input; |
| 640 | 701 | } |
| 641 | 702 | |
| 703 | + void startThread(br::FrameData * newItem) | |
| 704 | + { | |
| 705 | + BasicLoop * next = new BasicLoop(); | |
| 706 | + next->stages = stages; | |
| 707 | + next->start_idx = this->stage_id; | |
| 708 | + next->startItem = newItem; | |
| 709 | + this->threads->start(next, stages->size() - stage_id); | |
| 710 | + } | |
| 711 | + | |
| 642 | 712 | |
| 643 | 713 | // Calledfrom a different thread than run. |
| 644 | 714 | bool tryAcquireNextStage(FrameData *& input) |
| ... | ... | @@ -674,7 +744,7 @@ public: |
| 674 | 744 | }; |
| 675 | 745 | |
| 676 | 746 | // No input buffer, instead we draw templates from some data source |
| 677 | -// Will be operated by the main thread for the stream | |
| 747 | +// Will be operated by the main thread for the stream. starts threads | |
| 678 | 748 | class FirstStage : public SingleThreadStage |
| 679 | 749 | { |
| 680 | 750 | public: |
| ... | ... | @@ -684,44 +754,51 @@ public: |
| 684 | 754 | |
| 685 | 755 | FrameData * run(FrameData * input, bool & should_continue) |
| 686 | 756 | { |
| 687 | - // Is there anything on our input buffer? If so we should start a thread with that. | |
| 757 | + // Try to get a frame from the datasource | |
| 688 | 758 | QWriteLocker lock(&statusLock); |
| 689 | - input = dataSource.tryGetFrame(); | |
| 690 | - // Datasource broke? | |
| 691 | - if (!input) | |
| 759 | + bool last_frame = false; | |
| 760 | + input = dataSource.tryGetFrame(last_frame); | |
| 761 | + | |
| 762 | + // Datasource broke, or is currently out of frames? | |
| 763 | + if (!input || last_frame) | |
| 692 | 764 | { |
| 765 | + // We will just stop and not continue. | |
| 693 | 766 | currentStatus = STOPPING; |
| 694 | - should_continue = false; | |
| 695 | - return NULL; | |
| 767 | + if (!input) { | |
| 768 | + should_continue = false; | |
| 769 | + return NULL; | |
| 770 | + } | |
| 696 | 771 | } |
| 697 | 772 | lock.unlock(); |
| 698 | - | |
| 773 | + // Can we enter the next stage? | |
| 699 | 774 | should_continue = nextStage->tryAcquireNextStage(input); |
| 700 | 775 | |
| 701 | - BasicLoop * next = new BasicLoop(); | |
| 702 | - next->stages = stages; | |
| 703 | - next->start_idx = this->stage_id; | |
| 704 | - next->startItem = NULL; | |
| 705 | - | |
| 706 | - QThreadPool::globalInstance()->start(next, stages->size() - this->stage_id); | |
| 776 | + // We are exiting leaving this stage, should we start another | |
| 777 | + // thread here? Normally we will always re-queue a thread on | |
| 778 | + // the first stage, but if we received the last frame there is | |
| 779 | + // no need to. | |
| 780 | + if (!last_frame) { | |
| 781 | + startThread(NULL); | |
| 782 | + } | |
| 707 | 783 | |
| 708 | 784 | return input; |
| 709 | 785 | } |
| 710 | 786 | |
| 711 | - // Calledfrom a different thread than run. | |
| 787 | + // The last stage, trying to access the first stage | |
| 712 | 788 | bool tryAcquireNextStage(FrameData *& input) |
| 713 | 789 | { |
| 790 | + // Return the frame, was it the last one? | |
| 714 | 791 | bool was_last = dataSource.returnFrame(input); |
| 715 | 792 | input = NULL; |
| 793 | + | |
| 794 | + // OK we won't continue. | |
| 716 | 795 | if (was_last) { |
| 717 | 796 | return false; |
| 718 | 797 | } |
| 719 | 798 | |
| 720 | - if (!dataSource.isOpen()) | |
| 721 | - return false; | |
| 722 | - | |
| 723 | 799 | QReadLocker lock(&statusLock); |
| 724 | - // Thread is already running, we should just return | |
| 800 | + // A thread is already in the first stage, | |
| 801 | + // we should just return | |
| 725 | 802 | if (currentStatus == STARTING) |
| 726 | 803 | { |
| 727 | 804 | return false; |
| ... | ... | @@ -744,6 +821,7 @@ public: |
| 744 | 821 | |
| 745 | 822 | }; |
| 746 | 823 | |
| 824 | +// starts threads | |
| 747 | 825 | class LastStage : public SingleThreadStage |
| 748 | 826 | { |
| 749 | 827 | public: |
| ... | ... | @@ -774,11 +852,14 @@ public: |
| 774 | 852 | } |
| 775 | 853 | next_target = input->sequenceNumber + 1; |
| 776 | 854 | |
| 855 | + // add the item to our output buffer | |
| 777 | 856 | collectedOutput.append(input->data); |
| 778 | 857 | |
| 858 | + // Can we enter the read stage? | |
| 779 | 859 | should_continue = nextStage->tryAcquireNextStage(input); |
| 780 | 860 | |
| 781 | - // Is there anything on our input buffer? If so we should start a thread with that. | |
| 861 | + // Is there anything on our input buffer? If so we should start a thread | |
| 862 | + // in this stage to process that frame. | |
| 782 | 863 | QWriteLocker lock(&statusLock); |
| 783 | 864 | FrameData * newItem = inputBuffer->tryGetItem(); |
| 784 | 865 | if (!newItem) |
| ... | ... | @@ -788,23 +869,18 @@ public: |
| 788 | 869 | lock.unlock(); |
| 789 | 870 | |
| 790 | 871 | if (newItem) |
| 791 | - { | |
| 792 | - BasicLoop * next = new BasicLoop(); | |
| 793 | - next->stages = stages; | |
| 794 | - next->start_idx = this->stage_id; | |
| 795 | - next->startItem = newItem; | |
| 796 | - | |
| 797 | - QThreadPool::globalInstance()->start(next, stages->size() - this->stage_id); | |
| 798 | - } | |
| 872 | + startThread(newItem); | |
| 799 | 873 | |
| 800 | 874 | return input; |
| 801 | 875 | } |
| 802 | 876 | }; |
| 803 | 877 | |
| 878 | + | |
| 804 | 879 | class StreamTransform : public CompositeTransform |
| 805 | 880 | { |
| 806 | 881 | Q_OBJECT |
| 807 | 882 | public: |
| 883 | + | |
| 808 | 884 | void train(const TemplateList & data) |
| 809 | 885 | { |
| 810 | 886 | foreach(Transform * transform, transforms) { |
| ... | ... | @@ -834,21 +910,17 @@ public: |
| 834 | 910 | bool res = readStage->dataSource.open(dst); |
| 835 | 911 | if (!res) return; |
| 836 | 912 | |
| 837 | - QThreadPool::globalInstance()->releaseThread(); | |
| 913 | + // Start the first thread in the stream. | |
| 838 | 914 | readStage->currentStatus = SingleThreadStage::STARTING; |
| 915 | + readStage->startThread(NULL); | |
| 839 | 916 | |
| 840 | - BasicLoop loop; | |
| 841 | - loop.stages = &this->processingStages; | |
| 842 | - loop.start_idx = 0; | |
| 843 | - loop.startItem = NULL; | |
| 844 | - loop.setAutoDelete(false); | |
| 845 | - | |
| 846 | - QThreadPool::globalInstance()->start(&loop, processingStages.size() - processingStages[0]->stage_id); | |
| 847 | - | |
| 848 | - // Wait for the end. | |
| 917 | + // Wait for the stream to reach the last frame available from | |
| 918 | + // the data source. | |
| 849 | 919 | readStage->dataSource.waitLast(); |
| 850 | - QThreadPool::globalInstance()->reserveThread(); | |
| 851 | 920 | |
| 921 | + // Now that there are no more incoming frames, call finalize | |
| 922 | + // on each transform in turn to collect any last templates | |
| 923 | + // they wish to issue. | |
| 852 | 924 | TemplateList final_output; |
| 853 | 925 | |
| 854 | 926 | // Push finalize through the stages |
| ... | ... | @@ -864,7 +936,8 @@ public: |
| 864 | 936 | final_output.append(output_set); |
| 865 | 937 | } |
| 866 | 938 | |
| 867 | - // dst is set to all output received by the final stage | |
| 939 | + // dst is set to all output received by the final stage, along | |
| 940 | + // with anything output via the calls to finalize. | |
| 868 | 941 | dst = collectionStage->getOutput(); |
| 869 | 942 | dst.append(final_output); |
| 870 | 943 | |
| ... | ... | @@ -876,7 +949,8 @@ public: |
| 876 | 949 | virtual void finalize(TemplateList & output) |
| 877 | 950 | { |
| 878 | 951 | (void) output; |
| 879 | - // Not handling this yet -cao | |
| 952 | + // Nothing in particular to do here, stream calls finalize | |
| 953 | + // on all child transforms as part of projectUpdate | |
| 880 | 954 | } |
| 881 | 955 | |
| 882 | 956 | // Create and link stages |
| ... | ... | @@ -884,6 +958,19 @@ public: |
| 884 | 958 | { |
| 885 | 959 | if (transforms.isEmpty()) return; |
| 886 | 960 | |
| 961 | + // We share a thread pool across streams attached to the same | |
| 962 | + // parent tranform, retrieve or create a thread pool based | |
| 963 | + // on our parent transform. | |
| 964 | + QMutexLocker poolLock(&poolsAccess); | |
| 965 | + QHash<QObject *, QThreadPool *>::Iterator it; | |
| 966 | + if (!pools.contains(this->parent())) { | |
| 967 | + it = pools.insert(this->parent(), new QThreadPool(this)); | |
| 968 | + it.value()->setMaxThreadCount(Globals->parallelism); | |
| 969 | + } | |
| 970 | + else it = pools.find(this->parent()); | |
| 971 | + threads = it.value(); | |
| 972 | + poolLock.unlock(); | |
| 973 | + | |
| 887 | 974 | stage_variance.reserve(transforms.size()); |
| 888 | 975 | foreach (const br::Transform *transform, transforms) { |
| 889 | 976 | stage_variance.append(transform->timeVarying()); |
| ... | ... | @@ -894,6 +981,7 @@ public: |
| 894 | 981 | processingStages.push_back(readStage); |
| 895 | 982 | readStage->stage_id = 0; |
| 896 | 983 | readStage->stages = &this->processingStages; |
| 984 | + readStage->threads = this->threads; | |
| 897 | 985 | |
| 898 | 986 | int next_stage_id = 1; |
| 899 | 987 | |
| ... | ... | @@ -901,9 +989,7 @@ public: |
| 901 | 989 | for (int i =0; i < transforms.size(); i++) |
| 902 | 990 | { |
| 903 | 991 | if (stage_variance[i]) |
| 904 | - { | |
| 905 | 992 | processingStages.append(new SingleThreadStage(prev_stage_variance)); |
| 906 | - } | |
| 907 | 993 | else |
| 908 | 994 | processingStages.append(new MultiThreadStage(Globals->parallelism)); |
| 909 | 995 | |
| ... | ... | @@ -914,6 +1000,7 @@ public: |
| 914 | 1000 | processingStages[i]->nextStage = processingStages[i+1]; |
| 915 | 1001 | |
| 916 | 1002 | processingStages.last()->stages = &this->processingStages; |
| 1003 | + processingStages.last()->threads = this->threads; | |
| 917 | 1004 | |
| 918 | 1005 | processingStages.last()->transform = transforms[i]; |
| 919 | 1006 | prev_stage_variance = stage_variance[i]; |
| ... | ... | @@ -923,6 +1010,7 @@ public: |
| 923 | 1010 | processingStages.append(collectionStage); |
| 924 | 1011 | collectionStage->stage_id = next_stage_id; |
| 925 | 1012 | collectionStage->stages = &this->processingStages; |
| 1013 | + collectionStage->threads = this->threads; | |
| 926 | 1014 | |
| 927 | 1015 | processingStages[processingStages.size() - 2]->nextStage = collectionStage; |
| 928 | 1016 | |
| ... | ... | @@ -945,6 +1033,10 @@ protected: |
| 945 | 1033 | |
| 946 | 1034 | QList<ProcessingStage *> processingStages; |
| 947 | 1035 | |
| 1036 | + static QHash<QObject *, QThreadPool *> pools; | |
| 1037 | + static QMutex poolsAccess; | |
| 1038 | + QThreadPool * threads; | |
| 1039 | + | |
| 948 | 1040 | void _project(const Template &src, Template &dst) const |
| 949 | 1041 | { |
| 950 | 1042 | (void) src; (void) dst; |
| ... | ... | @@ -957,6 +1049,9 @@ protected: |
| 957 | 1049 | } |
| 958 | 1050 | }; |
| 959 | 1051 | |
| 1052 | +QHash<QObject *, QThreadPool *> StreamTransform::pools; | |
| 1053 | +QMutex StreamTransform::poolsAccess; | |
| 1054 | + | |
| 960 | 1055 | BR_REGISTER(Transform, StreamTransform) |
| 961 | 1056 | |
| 962 | 1057 | ... | ... |