From afdd6912e698d9990b4aef9aecdaeb51506c4b45 Mon Sep 17 00:00:00 2001 From: Zhengwu Date: Sun, 19 Oct 2025 00:32:10 +0800 Subject: [PATCH] ensure all rust threads are joined on err suppress b603 --- deps/SharedSrc.py | 45 +++++++++++++++------------- deps/rust_stdf_helper/src/lib.rs | 51 +++++++++++++++++++++++--------- 2 files changed, 61 insertions(+), 35 deletions(-) diff --git a/deps/SharedSrc.py b/deps/SharedSrc.py index 73e0ce7..a9f1f64 100644 --- a/deps/SharedSrc.py +++ b/deps/SharedSrc.py @@ -4,7 +4,7 @@ # Author: noonchen - chennoon233@foxmail.com # Created Date: November 5th 2022 # ----- -# Last Modified: Fri Oct 10 2025 +# Last Modified: Sun Nov 02 2025 # Modified By: noonchen # ----- # Copyright (c) 2022 noonchen @@ -121,7 +121,7 @@ def validate_symbols(cls, v: dict): for i, symbol in v.items(): try: i_num = int(i) - except: + except ValueError: continue if not isValidSymbol(symbol): @@ -633,30 +633,33 @@ def parseTestString(test_name_string: str, isWaferName: bool = False) -> tuple: return (test_num, pmr, test_name) -def openFileInOS(filepath: str): +def openOrRevealFileInOS(filepath: str, openf = True): # https://stackoverflow.com/a/435669 - filepath = os.path.normpath(filepath) - if platform.system() == 'Darwin': # macOS - subprocess.call(('open', filepath)) - elif platform.system() == 'Windows': # Windows - subprocess.call(f'cmd /c start "" "{filepath}"', creationflags = \ - subprocess.CREATE_NO_WINDOW | subprocess.DETACHED_PROCESS) - else: # linux variants - subprocess.call(('xdg-open', filepath)) - - -def revealFile(filepath: str): filepath = os.path.normpath(filepath) if not os.path.exists(filepath): return - + + flags = 0 if platform.system() == 'Darwin': # macOS - subprocess.call(('open', '-R', filepath)) + args = ( + ('open', filepath) if openf else + ('open', '-R', filepath) + ) + elif platform.system() == 'Windows': # Windows - subprocess.call(f'explorer /select,"{filepath}"', creationflags = \ - subprocess.CREATE_NO_WINDOW | subprocess.DETACHED_PROCESS) + args = ( + f'cmd /c start "" "{filepath}"' if openf else + f'explorer /select,"{filepath}"' + ) + flags = subprocess.CREATE_NO_WINDOW | subprocess.DETACHED_PROCESS + else: # linux variants - subprocess.call(('xdg-open', os.path.dirname(filepath))) + args = ( + ('xdg-open', filepath) if openf else + ('xdg-open', os.path.dirname(filepath)) + ) + + subprocess.call(args, creationflags = flags) # nosec def get_file_size(p: str) -> str: @@ -755,9 +758,9 @@ def showCompleteMessage(transFunc, outPath: str, title=None, infoText=None, icon msgbox.setDefaultButton(okBtn) msgbox.exec_() if msgbox.clickedButton() == revealBtn: - revealFile(outPath) + openOrRevealFileInOS(outPath, False) elif msgbox.clickedButton() == openBtn: - openFileInOS(outPath) + openOrRevealFileInOS(outPath, True) def validateSession(dbPath: str): diff --git a/deps/rust_stdf_helper/src/lib.rs b/deps/rust_stdf_helper/src/lib.rs index 8c626ba..6f57582 100644 --- a/deps/rust_stdf_helper/src/lib.rs +++ b/deps/rust_stdf_helper/src/lib.rs @@ -97,6 +97,29 @@ impl<'py> IntoPyObject<'py> for TestIDType { } } +// RAII to join threads +struct ThreadJoiner (Vec>); + +impl Drop for ThreadJoiner { + fn drop(&mut self) { + for handle in self.0.drain(..) { + if let Err(e) = handle.join() { + println!("Join thread failed: {:?}", e); + } + } + } +} + +// RAII to fill up progress bar +struct ProgressBarFiller(Arc); + +impl Drop for ProgressBarFiller { + fn drop(&mut self) { + // write 10000 as the sign of complete... + self.0.store(10000u16, Ordering::Relaxed); + } +} + /// Analyze record types in a STDF file #[pyfunction] #[pyo3(name = "analyzeSTDF")] @@ -651,6 +674,7 @@ fn generate_database( // start another thread for updating stop signal // and sending progress back to python let gil_th = thread::spawn(move || -> Result<(), StdfHelperError> { + let mut stop_cur_thread = false; loop { let current_progress = total_progress_copy.load(Ordering::Relaxed); // sleep for 100ms @@ -663,13 +687,12 @@ fn generate_database( .call_method1(intern!(py, "emit"), (current_progress,))?; } if is_valid_stop { - global_stop_copy.store( - stop_flag - .bind(py) - .getattr(intern!(py, "stop"))? - .extract::()?, - Ordering::Relaxed, - ); + let stop_from_py = stop_flag + .bind(py) + .getattr(intern!(py, "stop"))? + .extract::()?; + global_stop_copy.store(stop_from_py, Ordering::Relaxed); + stop_cur_thread |= stop_from_py; }; Ok(()) }) { @@ -678,7 +701,8 @@ fn generate_database( println!("{}", py_e); break; } - if current_progress == 10000 { + stop_cur_thread |= current_progress == 10000; + if stop_cur_thread { break; } } @@ -688,6 +712,11 @@ fn generate_database( } py.detach(|| -> Result<(), StdfHelperError> { + // use RAII to join threads + let _joiner = ThreadJoiner(thread_handles); + // use RAII to fill bar and stop gil thread + let _filler = ProgressBarFiller(total_progress.clone()); + // initiate sqlite3 database let conn = match Connection::open(&dbpath) { Ok(conn) => conn, @@ -741,13 +770,7 @@ fn generate_database( } // write HBR/SBR/TSR into database process_summary_data(&mut db_ctx, &mut record_tracker)?; - // write 10000 as the sign of complete... - total_progress.store(10000u16, Ordering::Relaxed); - // join threads - for handle in thread_handles { - handle.join().unwrap()?; - } // finalize database db_ctx.finalize(build_db_index)?; if let Err((_, err)) = conn.close() {