Skip to content

Commit d1eed6a

Browse files
committed
Fix the crash issue in LSTM multithreading
Changed the WERD_RES linked link to use shared pointers instead of raw pointers. This is needed so that even if one thread deletes a WERD_RES object, other thread's which needs to iterate thru them can still access it safely. In terms of LSTM processing, only one threads processes one WERD_RES. This change is needed as all the threads can iterate thru due to single linked list data structure.
1 parent b7d6739 commit d1eed6a

File tree

8 files changed

+338
-164
lines changed

8 files changed

+338
-164
lines changed

src/ccmain/control.cpp

+18-15
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,8 @@ bool Tesseract::RecogWordsSegment(std::vector<WordData>::iterator start,
203203
LSTMRecognizer *lstm_recognizer,
204204
std::atomic<int>& words_done,
205205
int total_words,
206-
std::mutex& monitor_mutex) {
207-
PAGE_RES_IT pr_it(page_res);
206+
std::shared_ptr<std::mutex> recog_words_mutex) {
207+
PAGE_RES_IT pr_it(page_res, recog_words_mutex);
208208
// Process a segment of the words vector
209209
pr_it.restart_page();
210210

@@ -214,7 +214,7 @@ bool Tesseract::RecogWordsSegment(std::vector<WordData>::iterator start,
214214
word->prev_word = &(*(it - 1));
215215
}
216216
if (monitor != nullptr) {
217-
std::lock_guard<std::mutex> lock(monitor_mutex);
217+
std::lock_guard<std::mutex> lock(*recog_words_mutex);
218218
monitor->ocr_alive = true;
219219
if (pass_n == 1) {
220220
monitor->progress = 70 * words_done / total_words;
@@ -245,9 +245,7 @@ bool Tesseract::RecogWordsSegment(std::vector<WordData>::iterator start,
245245
}
246246
}
247247
// Sync pr_it with the WordData.
248-
while (pr_it.word() != nullptr && pr_it.word() != word->word) {
249-
pr_it.forward();
250-
}
248+
pr_it.forward_to_word(word->word);
251249
ASSERT_HOST(pr_it.word() != nullptr);
252250
bool make_next_word_fuzzy = false;
253251
#ifndef DISABLED_LEGACY_ENGINE
@@ -274,19 +272,24 @@ bool Tesseract::RecogWordsSegment(std::vector<WordData>::iterator start,
274272
bool Tesseract::RecogAllWordsPassN(int pass_n, ETEXT_DESC *monitor, PAGE_RES *page_res,
275273
std::vector<WordData> *words) {
276274
int total_words = words->size();
277-
int segment_size = total_words / lstm_num_threads;
275+
int segment_size = std::max(total_words / lstm_num_threads, 1);
278276
std::atomic<int> words_done(0);
279-
std::mutex monitor_mutex;
277+
std::shared_ptr<std::mutex> recog_words_mutex = std::make_shared<std::mutex>();
280278
std::vector<std::future<bool>> futures;
281279

282280
// Launch multiple threads to recognize the words in parallel
283281
auto segment_start = words->begin() + segment_size;
284-
for (int i = 1; i < lstm_num_threads; ++i) {
285-
auto segment_end = (i == lstm_num_threads - 1) ? words->end() : segment_start + segment_size;
286-
futures.push_back(std::async(std::launch::async, &Tesseract::RecogWordsSegment,
287-
this, segment_start, segment_end, pass_n, monitor, page_res,
288-
lstm_recognizers_[i], std::ref(words_done), total_words, std::ref(monitor_mutex)));
289-
segment_start = segment_end;
282+
for (int i = 1; i < lstm_num_threads && segment_start != words->end(); ++i) {
283+
auto segment_end = segment_start + segment_size;
284+
if (i == lstm_num_threads - 1 ||
285+
std::distance(segment_start, words->end()) < segment_size) {
286+
segment_end = words->end();
287+
}
288+
futures.push_back(std::async(
289+
std::launch::async, &Tesseract::RecogWordsSegment, this, segment_start,
290+
segment_end, pass_n, monitor, page_res, lstm_recognizers_[i],
291+
std::ref(words_done), total_words, recog_words_mutex));
292+
segment_start = segment_end;
290293
}
291294

292295
// Process the first segment in this thread
@@ -298,7 +301,7 @@ bool Tesseract::RecogAllWordsPassN(int pass_n, ETEXT_DESC *monitor, PAGE_RES *pa
298301
lstm_recognizers_[0],
299302
std::ref(words_done),
300303
total_words,
301-
std::ref(monitor_mutex));
304+
recog_words_mutex);
302305

303306
// Wait for all threads to complete and aggregate results
304307
for (auto &f : futures) {

src/ccmain/fixspace.cpp

+20-20
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ void Tesseract::fix_fuzzy_spaces(ETEXT_DESC *monitor, int32_t word_count, PAGE_R
7979
ROW_RES_IT row_res_it;
8080
WERD_RES_IT word_res_it_from;
8181
WERD_RES_IT word_res_it_to;
82-
WERD_RES *word_res;
82+
std::shared_ptr<WERD_RES> word_res;
8383
WERD_RES_LIST fuzzy_space_words;
8484
int16_t new_length;
8585
bool prevent_null_wd_fixsp; // DON'T process blobless wds
@@ -114,7 +114,7 @@ void Tesseract::fix_fuzzy_spaces(ETEXT_DESC *monitor, int32_t word_count, PAGE_R
114114
if (!word_res_it_from.at_last()) {
115115
word_res_it_to = word_res_it_from;
116116
prevent_null_wd_fixsp = word_res->word->cblob_list()->empty();
117-
if (check_debug_pt(word_res, 60)) {
117+
if (check_debug_pt(word_res.get(), 60)) {
118118
debug_fix_space_level.set_value(10);
119119
}
120120
word_res_it_to.forward();
@@ -131,15 +131,15 @@ void Tesseract::fix_fuzzy_spaces(ETEXT_DESC *monitor, int32_t word_count, PAGE_R
131131
while (!word_res_it_to.at_last() &&
132132
(word_res_it_to.data_relative(1)->word->flag(W_FUZZY_NON) ||
133133
word_res_it_to.data_relative(1)->word->flag(W_FUZZY_SP))) {
134-
if (check_debug_pt(word_res, 60)) {
134+
if (check_debug_pt(word_res.get(), 60)) {
135135
debug_fix_space_level.set_value(10);
136136
}
137137
if (word_res->word->cblob_list()->empty()) {
138138
prevent_null_wd_fixsp = true;
139139
}
140140
word_res = word_res_it_to.forward();
141141
}
142-
if (check_debug_pt(word_res, 60)) {
142+
if (check_debug_pt(word_res.get(), 60)) {
143143
debug_fix_space_level.set_value(10);
144144
}
145145
if (word_res->word->cblob_list()->empty()) {
@@ -203,12 +203,12 @@ void initialise_search(WERD_RES_LIST &src_list, WERD_RES_LIST &new_list) {
203203
WERD_RES *new_wd;
204204

205205
for (src_it.mark_cycle_pt(); !src_it.cycled_list(); src_it.forward()) {
206-
WERD_RES *src_wd = src_it.data();
206+
WERD_RES *src_wd = src_it.data().get();
207207
if (!src_wd->combination) {
208208
new_wd = WERD_RES::deep_copy(src_wd);
209209
new_wd->combination = false;
210210
new_wd->part_of_combo = false;
211-
new_it.add_after_then_move(new_wd);
211+
new_it.add_after_then_move(std::shared_ptr<WERD_RES>(new_wd));
212212
}
213213
}
214214
}
@@ -220,7 +220,7 @@ void Tesseract::match_current_words(WERD_RES_LIST &words, ROW *row, BLOCK *block
220220
// prev_word_best_choice_ before calling classify_word_pass2().
221221
prev_word_best_choice_ = nullptr;
222222
for (word_it.mark_cycle_pt(); !word_it.cycled_list(); word_it.forward()) {
223-
word = word_it.data();
223+
word = word_it.data().get();
224224
if ((!word->part_of_combo) && (word->box_word == nullptr)) {
225225
WordData word_data(block, row, word);
226226
SetupWordPassN(2, &word_data);
@@ -269,7 +269,7 @@ int16_t Tesseract::eval_word_spacing(WERD_RES_LIST &word_res_list) {
269269
const char *punct_chars = "!\"`',.:;";
270270
do {
271271
// current word
272-
WERD_RES *word = word_res_it.data();
272+
WERD_RES *word = word_res_it.data().get();
273273
bool word_done = fixspace_thinks_word_done(word);
274274
word_count++;
275275
if (word->tess_failed) {
@@ -396,7 +396,7 @@ void transform_to_next_perm(WERD_RES_LIST &words) {
396396
int16_t min_gap = INT16_MAX;
397397

398398
for (word_it.mark_cycle_pt(); !word_it.cycled_list(); word_it.forward()) {
399-
word = word_it.data();
399+
word = word_it.data().get();
400400
if (!word->part_of_combo) {
401401
box = word->word->bounding_box();
402402
if (prev_right > -INT16_MAX) {
@@ -413,13 +413,13 @@ void transform_to_next_perm(WERD_RES_LIST &words) {
413413
word_it.set_to_list(&words);
414414
// Note: we can't use cycle_pt due to inserted combos at start of list.
415415
for (; (prev_right == -INT16_MAX) || !word_it.at_first(); word_it.forward()) {
416-
word = word_it.data();
416+
word = word_it.data().get();
417417
if (!word->part_of_combo) {
418418
box = word->word->bounding_box();
419419
if (prev_right > -INT16_MAX) {
420420
gap = box.left() - prev_right;
421421
if (gap <= min_gap) {
422-
prev_word = prev_word_it.data();
422+
prev_word = prev_word_it.data().get();
423423
WERD_RES *combo;
424424
if (prev_word->combination) {
425425
combo = prev_word;
@@ -433,14 +433,14 @@ void transform_to_next_perm(WERD_RES_LIST &words) {
433433
combo->combination = true;
434434
combo->x_height = prev_word->x_height;
435435
prev_word->part_of_combo = true;
436-
prev_word_it.add_before_then_move(combo);
436+
prev_word_it.add_before_then_move(std::shared_ptr<WERD_RES>(combo));
437437
}
438438
combo->word->set_flag(W_EOL, word->word->flag(W_EOL));
439439
if (word->combination) {
440440
combo->word->join_on(word->word);
441441
// Move blobs to combo
442442
// old combo no longer needed
443-
delete word_it.extract();
443+
word_it.extract();
444444
} else {
445445
// Copy current wd to combo
446446
combo->copy_on(word);
@@ -545,7 +545,7 @@ void Tesseract::fix_sp_fp_word(WERD_RES_IT &word_res_it, ROW *row, BLOCK *block)
545545
int16_t new_length;
546546
float junk;
547547

548-
word_res = word_res_it.data();
548+
word_res = word_res_it.data().get();
549549
if (word_res->word->flag(W_REP_CHAR) || word_res->combination || word_res->part_of_combo ||
550550
!word_res->word->flag(W_DONT_CHOP)) {
551551
return;
@@ -582,11 +582,11 @@ void Tesseract::fix_noisy_space_list(WERD_RES_LIST &best_perm, ROW *row, BLOCK *
582582

583583
dump_words(best_perm, best_score, 1, improved);
584584

585-
old_word_res = best_perm_it.data();
585+
old_word_res = best_perm_it.data().get();
586586
// Even deep_copy doesn't copy the underlying WERD unless its combination
587587
// flag is true!.
588588
old_word_res->combination = true; // Kludge to force deep copy
589-
current_perm_it.add_to_end(WERD_RES::deep_copy(old_word_res));
589+
current_perm_it.add_to_end(std::shared_ptr<WERD_RES>(WERD_RES::deep_copy(old_word_res)));
590590
old_word_res->combination = false; // Undo kludge
591591

592592
break_noisiest_blob_word(current_perm);
@@ -630,7 +630,7 @@ void Tesseract::break_noisiest_blob_word(WERD_RES_LIST &words) {
630630
int16_t i;
631631

632632
for (word_it.mark_cycle_pt(); !word_it.cycled_list(); word_it.forward()) {
633-
auto blob_index = worst_noise_blob(word_it.data(), &noise_score);
633+
auto blob_index = worst_noise_blob(word_it.data().get(), &noise_score);
634634
if (blob_index > -1 && worst_noise_score > noise_score) {
635635
worst_noise_score = noise_score;
636636
worst_blob_index = blob_index;
@@ -644,7 +644,7 @@ void Tesseract::break_noisiest_blob_word(WERD_RES_LIST &words) {
644644

645645
/* Now split the worst_word_it */
646646

647-
word_res = worst_word_it.data();
647+
word_res = worst_word_it.data().get();
648648

649649
/* Move blobs before noise blob to a new bloblist */
650650

@@ -671,7 +671,7 @@ void Tesseract::break_noisiest_blob_word(WERD_RES_LIST &words) {
671671

672672
auto *new_word_res = new WERD_RES(new_word);
673673
new_word_res->combination = true;
674-
worst_word_it.add_before_then_move(new_word_res);
674+
worst_word_it.add_before_then_move(std::shared_ptr<WERD_RES>(new_word_res));
675675

676676
word_res->ClearResults();
677677
}
@@ -834,7 +834,7 @@ int16_t Tesseract::fp_eval_word_spacing(WERD_RES_LIST &word_res_list) {
834834
float small_limit = kBlnXHeight * fixsp_small_outlines_size;
835835

836836
for (word_it.mark_cycle_pt(); !word_it.cycled_list(); word_it.forward()) {
837-
word = word_it.data();
837+
word = word_it.data().get();
838838
if (word->rebuild_word == nullptr) {
839839
continue; // Can't handle cube words.
840840
}

src/ccmain/tesseractclass.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ class TESS_API Tesseract : public Wordrec {
387387
bool RecogWordsSegment(std::vector<WordData>::iterator start, std::vector<WordData>::iterator end,
388388
int pass_n, ETEXT_DESC *monitor, PAGE_RES *page_res,
389389
LSTMRecognizer *lstm_recognizer, std::atomic<int>& words_done,
390-
int total_words, std::mutex& monitor_mutex);
390+
int total_words, std::shared_ptr<std::mutex> recog_words_mutex);
391391
// Runs word recognition on all the words.
392392
bool RecogAllWordsPassN(int pass_n, ETEXT_DESC *monitor, PAGE_RES *page_res,
393393
std::vector<WordData> *words);

0 commit comments

Comments
 (0)