Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/build-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,33 @@ jobs:
fi
echo "[str positions] every string position is a character"

- name: A run past -TIMELIMIT is stopped (Non-20.04)
if: matrix.id != 'docker-ubuntu-20.04'
timeout-minutes: 3
shell: bash
run: |
set -o pipefail
# A loop that never ends used to run until something killed the
# process; one that makes a string a turn, until it ran out of
# memory. With -TIMELIMIT the run stops at the next while turn past
# the limit: the code after the loop and the passes after it do not
# run, the stop is reported on stderr, and nlp exits 2.
FIX=.github/workflows/tests/run-limit
rm -rf "$FIX/logs" "$FIX/input/text.txt_log"
mkdir -p "$FIX/logs"
rc=0
./bin/nlp -TIMELIMIT 2 -ANA "$FIX" -WORK ./ "$FIX/input/text.txt" 2> /tmp/runlimit.err || rc=$?
if [ "$rc" != 2 ]; then
echo "FAIL(run limit): exit $rc, wanted 2"; cat /tmp/runlimit.err; exit 1
fi
got=$(tr -d '\r' < "$FIX/input/text.txt_log/out.txt")
if [ "$got" != "before" ]; then
echo "FAIL(run limit): out.txt was:"; echo "$got"; echo "wanted: before"; exit 1
fi
grep -qF '[Run stopped: past its time limit of 2000 ms in pass 3 (loop)' /tmp/runlimit.err \
|| { echo "FAIL(run limit): no stop message on stderr"; cat /tmp/runlimit.err; exit 1; }
echo "[run limit] a loop that never ends is stopped at -TIMELIMIT"

- name: Run-mode globals ($dev / $silent, Non-20.04)
if: matrix.id != 'docker-ubuntu-20.04'
shell: bash
Expand Down
26 changes: 26 additions & 0 deletions .github/workflows/build-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,32 @@ jobs:
fi
echo "[str positions] every string position is a character"

- name: A run past -TIMELIMIT is stopped
timeout-minutes: 3
shell: bash
run: |
set -o pipefail
# A loop that never ends used to run until something killed the
# process; one that makes a string a turn, until it ran out of
# memory. With -TIMELIMIT the run stops at the next while turn past
# the limit: the code after the loop and the passes after it do not
# run, the stop is reported on stderr, and nlp exits 2.
FIX=.github/workflows/tests/run-limit
rm -rf "$FIX/logs" "$FIX/input/text.txt_log"
mkdir -p "$FIX/logs"
rc=0
./bin/nlp -TIMELIMIT 2 -ANA "$FIX" -WORK ./ "$FIX/input/text.txt" 2> /tmp/runlimit.err || rc=$?
if [ "$rc" != 2 ]; then
echo "FAIL(run limit): exit $rc, wanted 2"; cat /tmp/runlimit.err; exit 1
fi
got=$(tr -d '\r' < "$FIX/input/text.txt_log/out.txt")
if [ "$got" != "before" ]; then
echo "FAIL(run limit): out.txt was:"; echo "$got"; echo "wanted: before"; exit 1
fi
grep -qF '[Run stopped: past its time limit of 2000 ms in pass 3 (loop)' /tmp/runlimit.err \
|| { echo "FAIL(run limit): no stop message on stderr"; cat /tmp/runlimit.err; exit 1; }
echo "[run limit] a loop that never ends is stopped at -TIMELIMIT"

# INTEL MACS (#231). The steps above built and tested arm64 (Apple
# Silicon). Build the same tree for x86_64 too and merge the two with
# lipo, so nlpm.exe and the compile libraries are universal binaries:
Expand Down
26 changes: 26 additions & 0 deletions .github/workflows/build-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,32 @@ jobs:
fi
echo "[str positions] every string position is a character"

- name: A run past -TIMELIMIT is stopped
timeout-minutes: 3
shell: bash
run: |
set -o pipefail
# A loop that never ends used to run until something killed the
# process; one that makes a string a turn, until it ran out of
# memory. With -TIMELIMIT the run stops at the next while turn past
# the limit: the code after the loop and the passes after it do not
# run, the stop is reported on stderr, and nlp exits 2.
FIX=.github/workflows/tests/run-limit
rm -rf "$FIX/logs" "$FIX/input/text.txt_log"
mkdir -p "$FIX/logs"
rc=0
./bin/Release/nlp.exe -TIMELIMIT 2 -ANA "$FIX" -WORK ./ "$FIX/input/text.txt" 2> /tmp/runlimit.err || rc=$?
if [ "$rc" != 2 ]; then
echo "FAIL(run limit): exit $rc, wanted 2"; cat /tmp/runlimit.err; exit 1
fi
got=$(tr -d '\r' < "$FIX/input/text.txt_log/out.txt")
if [ "$got" != "before" ]; then
echo "FAIL(run limit): out.txt was:"; echo "$got"; echo "wanted: before"; exit 1
fi
grep -qF '[Run stopped: past its time limit of 2000 ms in pass 3 (loop)' /tmp/runlimit.err \
|| { echo "FAIL(run limit): no stop message on stderr"; cat /tmp/runlimit.err; exit 1; }
echo "[run limit] a loop that never ends is stopped at -TIMELIMIT"

- name: Run-mode globals ($dev / $silent)
shell: bash
run: |
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/tests/run-limit/input/text.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The fixture loops; the text does not matter.
Empty file.
3 changes: 3 additions & 0 deletions .github/workflows/tests/run-limit/spec/after.nlp
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@CODE
"out.txt" << "after-pass\n";
@@CODE
4 changes: 4 additions & 0 deletions .github/workflows/tests/run-limit/spec/analyzer.seq
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
tokenize nil # tokenize only -- fixture needs no dictionary
nlp before # runs, and says so
nlp loop # never ends on its own
nlp after # must not run once the limit stops the loop
3 changes: 3 additions & 0 deletions .github/workflows/tests/run-limit/spec/before.nlp
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@CODE
"out.txt" << "before\n";
@@CODE
23 changes: 23 additions & 0 deletions .github/workflows/tests/run-limit/spec/loop.nlp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
###############################################
# FILE: loop.nlp
# SUBJ: A loop that never ends is stopped by -TIMELIMIT.
# NOTE: Like the loop that killed a document extractor: every turn
# makes a new string, so without a limit it runs until the
# process is killed for its memory. With -TIMELIMIT 2 the run
# stops at the next while turn after two seconds; the line after
# the loop and the pass after this one do not run, and nlp exits 2.
# Expected out.txt:
# before
###############################################

@CODE

L("s") = "x";
L("n") = 0;
while (L("n") >= 0) {
L("n")++;
L("s") = "x" + str(L("n"));
}
"out.txt" << "after-loop\n";

@@CODE
1 change: 1 addition & 0 deletions include/Api/lite/Arun.h
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,7 @@ class LITE_API Arun
static bool iassign(int,_TCHAR*,int,long long,Nlppp*,bool); // 12/10/02 AM.
static long long iassign(int,_TCHAR*,int,long long,Nlppp*,int);

static bool overbudget(Nlppp*); // Run past its time limit. // 10/25/26 DD.
static bool truth(long long);
static bool truth(float);
static bool truth(_TCHAR *);
Expand Down
12 changes: 12 additions & 0 deletions include/Api/lite/nlp.h
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,18 @@ class LITE_API NLP
// Intended for notable errors. // VTLOG // 05/06/13 AM.
_TCHAR *logfile_; // VTLOG // 05/06/13 AM.

// RUN LIMIT for each text's analysis, in milliseconds (0 = none), and
// whether a run since the flag was cleared was stopped at it. Each new
// Parse takes the limit; Parse::overBudget sets the flag. // 10/25/26 DD.
public:
void setRunLimit(long long ms) { runLimitMs_ = ms > 0 ? ms : 0; }
long long getRunLimit() { return runLimitMs_; }
void setRunStopped(bool stopped) { runStopped_ = stopped; }
bool getRunStopped() { return runStopped_; }
private:
long long runLimitMs_ = 0;
bool runStopped_ = false;

// When code is well debugged and stable, can remove the stuff below.
#ifndef STABLE_
public:
Expand Down
10 changes: 10 additions & 0 deletions include/Api/lite/nlp_engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,14 @@ class LITE_API NLP_ENGINE
void setDatum(const _TCHAR *datum);
const _TCHAR *getDatum();

// A time limit on each text's analysis, in milliseconds; 0 (the
// default) is none. A run past it is stopped -- at its next while turn
// or pass -- instead of looping until something kills the process, and
// lastRunStopped() is true after the analyze() call that stopped one.
void setRunLimit(long long ms);
long long getRunLimit();
bool lastRunStopped();

private:
void analyzeFile(const std::string &file, _TCHAR *outdir, std::_t_ofstream *fp);
int createDir(_TCHAR *dirPath);
Expand All @@ -181,7 +189,9 @@ class LITE_API NLP_ENGINE
// passed as different path-forms maps to one entry -- see #658 / #380.
void anaRegName(const _TCHAR *path, _TCHAR *out);
_TCHAR *datumArg();
void armRunLimit();

// At the end of the class, so its layout is unchanged before it.
std::basic_string<_TCHAR> m_datum; // G("$datum") for analyses.
long long m_runLimitMs = 0; // setRunLimit; 0 = none.
};
15 changes: 15 additions & 0 deletions include/Api/lite/parse.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ All rights reserved.
#endif
#include <time.h>
#include <set>
#include <chrono>

typedef void TREE; // 07/07/03 AM.
typedef void LTREE; // 07/07/03 AM.
Expand Down Expand Up @@ -658,6 +659,20 @@ class LITE_API Parse
// Pass info to analyzer, to G("$datum") variable. // 03/13/03 AM.
_TCHAR *datum_; // 03/13/03 AM.

// RUN LIMIT. A time limit on this text's analysis, so an analyzer whose
// loop never ends is stopped instead of taking its process with it: every
// turn of a while loop, and every pass, asks overBudget(). 0, the
// default, is no limit. // 10/25/26 DD.
public:
void setRunLimit(long long ms);
bool overBudget(bool now=false); // True once past the limit; stays true.
bool runStopped() { return runStopped_; }
private:
long long runLimitMs_ = 0;
std::chrono::steady_clock::time_point runDeadline_;
unsigned long runTicks_ = 0; // Calls since the clock was last read.
bool runStopped_ = false;

#ifndef STABLE_
public:
static int getCount();
Expand Down
1 change: 1 addition & 0 deletions include/Api/my_tchar.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ All rights reserved.
#define _tcsnccpy strncpy
#define _tgetenv getenv
#define _ttoi atoi
#define _tstof atof // nlp -TIMELIMIT seconds. // 10/25/26 DD.
#define _tsystem system
#define _stprintf sprintf
#define _ftprintf fprintf
Expand Down
14 changes: 14 additions & 0 deletions lite/Arun.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8425,6 +8425,20 @@ return iassign(typ, varname, nelt, index, nlppp, (long long)val);
}


/********************************************
* FN: OVERBUDGET
* CR: 10/25/26 DD.
* SUBJ: Has this run passed its time limit? Compiled runtime.
* NOTE: Generated into every compiled while condition, as the interpreter
* asks Parse::overBudget on every turn (Iwhilestmt::eval).
********************************************/

bool Arun::overbudget(Nlppp *nlppp)
{
return nlppp && nlppp->parse_ && nlppp->parse_->overBudget();
}


/********************************************
* FN: TRUTH
* CR: 05/13/00 AM.
Expand Down
12 changes: 11 additions & 1 deletion lite/iwhilestmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,15 @@ _TCHAR *str;

for (;;) // EXECUTE THE WHILE-LOOP.
{
// A run past its time limit (Parse::setRunLimit) stops here, and fails
// the region: the code after a loop that did not finish would run on
// whatever it left half done. Parse::overBudget has said why. // 10/25/26 DD.
if (nlppp->parse_->overBudget())
{
ok = false;
goto done;
}

ok = cond_->eval(nlppp, /*UP*/ c_val);
if (!ok || !c_val)
{
Expand Down Expand Up @@ -478,7 +487,8 @@ enum RFASemtype typ = RSNULL;
// function with all possible return types! Elegant! Can be an inline.
// This can be a general way....(but not totally optimized.)
// If var, evals it. (see Iwhilestmt::eval).
*fcode << indent << _T("while (Arun::truth("); // FIX! // 04/29/01 AM.
// The run limit applies to compiled analyzers too (see eval). // 10/25/26 DD.
*fcode << indent << _T("while (!Arun::overbudget(nlppp) && Arun::truth(");
if (!cond_->genEval(gen))
{
*fcode << _T("\n// ERROR in whilestmt condition.") << std::endl;
Expand Down
2 changes: 2 additions & 0 deletions lite/nlp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1462,6 +1462,7 @@ if (!htab_) // 06/25/03 AM.
Parse *parse; // The current text analysis instance.
parse = new Parse(); // 05/13/99 AM.
parse->nlp_ = this; // 06/25/03 AM.
parse->setRunLimit(runLimitMs_); // 0 = none; see Parse::overBudget. // 10/25/26 DD.
parse->htab_ = this->htab_; // 06/25/03 AM.

parse->setEana(eana); // 10/13/99 AM.
Expand Down Expand Up @@ -1608,6 +1609,7 @@ if (!htab_) // 06/25/03 AM.
Parse *parse; // The current text analysis instance.
parse = new Parse(); // 05/13/99 AM.
parse->nlp_ = this; // 06/25/03 AM.
parse->setRunLimit(runLimitMs_); // 0 = none; see Parse::overBudget. // 10/25/26 DD.
parse->htab_ = this->htab_; // 06/25/03 AM.

parse->setEana(eana); // 10/13/99 AM.
Expand Down
27 changes: 27 additions & 0 deletions lite/nlp_engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,7 @@ int NLP_ENGINE::analyze(

if (!m_nlp) // defensive: never analyze without a live analyzer object.
return -1;
armRunLimit();

// #639: a missing input-file argument (easy to hit with -COMPILED, whose
// syntax differs from -COMPILE only by the trailing input path) used to
Expand Down Expand Up @@ -514,6 +515,7 @@ int NLP_ENGINE::analyzeStream(

if (!m_nlp) // defensive: never analyze without a live analyzer object.
return -1;
armRunLimit();

std::filesystem::path fname(analyzer);
fname /= _T("logs");
Expand Down Expand Up @@ -670,6 +672,7 @@ int NLP_ENGINE::analyze(

if (!m_nlp) // defensive: never analyze without a live analyzer object.
return -1;
armRunLimit();

// The analyzer's cout() writes to standard output.

Expand Down Expand Up @@ -722,6 +725,7 @@ int NLP_ENGINE::analyze(

if (!m_nlp) // defensive: never analyze without a live analyzer object.
return -1;
armRunLimit();

// The analyzer's cout() writes to standard output.

Expand Down Expand Up @@ -826,6 +830,29 @@ const _TCHAR *NLP_ENGINE::getDatum() {
return m_datum.c_str();
}

// A time limit on each text's analysis, in milliseconds; 0 is none. A run
// past it stops at its next while turn or pass, and lastRunStopped() says
// so. See Parse::overBudget for why. // 10/25/26 DD.
void NLP_ENGINE::setRunLimit(long long ms) {
m_runLimitMs = ms > 0 ? ms : 0;
}

long long NLP_ENGINE::getRunLimit() {
return m_runLimitMs;
}

// Whether the last analyze() or analyzeStream() call stopped a run (any of
// its inputs) at the time limit.
bool NLP_ENGINE::lastRunStopped() {
return m_nlp && m_nlp->getRunStopped();
}

// init() may have made a new NLP; give it the limit, and clear the flag.
void NLP_ENGINE::armRunLimit() {
m_nlp->setRunLimit(m_runLimitMs);
m_nlp->setRunStopped(false);
}

// The datum to pass to an analysis: 0 when unset, as before.
_TCHAR *NLP_ENGINE::datumArg() {
return m_datum.empty() ? 0 : (_TCHAR *) m_datum.c_str();
Expand Down
Loading
Loading