diff --git a/crates/tb-cli/tests/compat.rs b/crates/tb-cli/tests/compat.rs
index e01dbd8..1a3c234 100644
--- a/crates/tb-cli/tests/compat.rs
+++ b/crates/tb-cli/tests/compat.rs
@@ -13,6 +13,9 @@
//! nur die untere Schranke, nie eine feste Größe, und der Korpus muss das
//! nachweisen können.
+#[path = "../../../tests/support/prozessfrist.rs"]
+mod prozessfrist;
+
use std::collections::VecDeque;
use std::path::{Path, PathBuf};
use std::process::Command;
@@ -354,6 +357,10 @@ impl Drop for TempVerzeichnis {
/// Korpusdatei ausführen und den Bildschirm-Snapshot liefern.
fn run_corpus_file(path: &Path, groesse: Option<(usize, usize)>) -> String {
+ let _frist = prozessfrist::Prozessfrist::neu(
+ &path.display().to_string(),
+ std::time::Duration::from_secs(30),
+ );
let raw = std::fs::read_to_string(path).unwrap();
let name = path.file_name().unwrap().to_string_lossy().to_string();
let form = if path
@@ -608,6 +615,51 @@ fn tbc_run_hello() {
assert_eq!(String::from_utf8_lossy(&out.stdout), "Hallo, Welt!\n");
}
+/// OPEN darf keinen zweiten Writer auf dieselbe Datenbank anfordern.
+/// Der Unterprozess wird auch bei einer Regression nach fünf Sekunden beendet.
+#[test]
+fn tbc_isam_open_in_transaktion_hat_eine_harte_frist() {
+ use std::process::Stdio;
+ use std::time::{Duration, Instant};
+
+ let dir = std::env::temp_dir().join(format!("tb_isam_open_{}", std::process::id()));
+ let _ = std::fs::remove_dir_all(&dir);
+ std::fs::create_dir_all(&dir).unwrap();
+ let mut child = Command::new(env!("CARGO_BIN_EXE_tbc"))
+ .arg("run")
+ .arg(compat_dir().join("isamopen.bas"))
+ .current_dir(&dir)
+ .stdin(Stdio::null())
+ .stdout(Stdio::piped())
+ .stderr(Stdio::piped())
+ .spawn()
+ .expect("tbc startet");
+ let start = Instant::now();
+ let beendet = loop {
+ match child.try_wait() {
+ Ok(Some(_)) => break true,
+ Ok(None) if start.elapsed() < Duration::from_secs(5) => {
+ std::thread::sleep(Duration::from_millis(10));
+ }
+ _ => {
+ let _ = child.kill();
+ break false;
+ }
+ }
+ };
+ let out = child.wait_with_output().unwrap();
+ let _ = std::fs::remove_dir_all(&dir);
+ assert!(
+ beendet,
+ "OPEN innerhalb einer Transaktion hat die Prozessfrist überschritten: {out:?}"
+ );
+ assert!(out.status.success(), "{out:?}");
+ assert_eq!(
+ String::from_utf8_lossy(&out.stdout),
+ include_str!("../../../tests/compat/isamopen.out")
+ );
+}
+
#[test]
fn tbc_run_stop_exitcode() {
let exe = env!("CARGO_BIN_EXE_tbc");
diff --git a/crates/tb-runtime/src/builtins.rs b/crates/tb-runtime/src/builtins.rs
index b50df63..bcf5cf6 100644
--- a/crates/tb-runtime/src/builtins.rs
+++ b/crates/tb-runtime/src/builtins.rs
@@ -1283,6 +1283,9 @@ fn bi_open(
let nummer = arg_i32(a, 1)?;
let modus = fio::Modus::aus_text(&arg_str(a, 2)?).ok_or(RuntimeError::ILLEGAL_FUNCTION_CALL)?;
let reclen = arg_i32(a, 3)?.max(1) as usize;
+ if st.isam.ist_isam(nummer) {
+ return Err(RuntimeError::FILE_ALREADY_OPEN);
+ }
st.dateien
.oeffnen(nummer, &pfad, modus, reclen)
.map(|_| None)
@@ -1305,7 +1308,7 @@ fn bi_close_all(
_: &mut dyn Host,
_: &mut [Value],
) -> Result