Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LDEV-5152 thread action=join throwOnError #2441

Open
wants to merge 1 commit into
base: 6.2
Choose a base branch
from
Open
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
40 changes: 29 additions & 11 deletions core/src/main/java/lucee/runtime/tag/ThreadTag.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import lucee.runtime.spooler.ExecutionPlan;
import lucee.runtime.spooler.ExecutionPlanImpl;
import lucee.runtime.spooler.SpoolerEngineImpl;
import lucee.runtime.tag.Throw;
import lucee.runtime.thread.ChildSpoolerTask;
import lucee.runtime.thread.ChildThread;
import lucee.runtime.thread.ChildThreadImpl;
Expand Down Expand Up @@ -96,6 +97,7 @@ public final class ThreadTag extends BodyTagImpl implements DynamicAttributes {
private ExecutionPlan[] plans = EXECUTION_PLAN;
private Struct attrs;
private boolean separateScopes = true;
private boolean throwonerror = false;

@Override
public void release() {
Expand All @@ -110,6 +112,7 @@ public void release() {
attrs = null;
pc = null;
separateScopes = true;
throwonerror = false;
}

/**
Expand All @@ -122,7 +125,7 @@ public void setAction(String strAction) throws ApplicationException {
else if ("run".equals(lcAction)) this.action = ACTION_RUN;
else if ("sleep".equals(lcAction)) this.action = ACTION_SLEEP;
else if ("terminate".equals(lcAction)) this.action = ACTION_TERMINATE;
else throw new ApplicationException("invalid value [" + strAction + "] for attribute action", "values for attribute action are:join,run,sleep,terminate");
else throw new ApplicationException("Invalid value [" + strAction + "] for attribute [action]", "values for attribute action are: [join, run, sleep, terminate]");
}

/**
Expand All @@ -144,6 +147,15 @@ public void setName(String name) {
this.name = KeyImpl.init(name);
}

/**
* @param throwonerror value to set
**/
public void setThrowonerror(boolean throwonerror) throws ApplicationException {
if (this.action != ACTION_JOIN && throwonerror) throw new ApplicationException("Attribute [throwonerror] is only supported for action [join]");
this.throwonerror = throwonerror;
}


private Collection.Key name(boolean create) {
if (name == null && create) name = KeyImpl.init("thread" + RandomUtil.createRandomStringLC(5));
return name;
Expand All @@ -160,7 +172,7 @@ public String nameAsString(boolean create) {
public void setPriority(String strPriority) throws ApplicationException {
int p = ThreadUtil.toIntPriority(strPriority);
if (p == -1) {
throw new ApplicationException("invalid value [" + strPriority + "] for attribute priority", "values for attribute priority are:low,high,normal");
throw new ApplicationException("Invalid value [" + strPriority + "] for attribute [priority]", "values for attribute [priority] are: [low, high, normal]");
}
priority = p;
}
Expand Down Expand Up @@ -190,7 +202,7 @@ else if ("daemon".equals(strType)) {
type = TYPE_DAEMON;
}
else {
throw new ApplicationException("invalid value [" + strType + "] for attribute type", "values for attribute type are:task,daemon (default)");
throw new ApplicationException("Invalid value [" + strType + "] for attribute [type]", "Supported values for attribute [type] are: [task,daemon (default)]");
}
}

Expand Down Expand Up @@ -223,17 +235,17 @@ private ExecutionPlan toExecutionPlan(Object obj, int plus) throws PageException

// tries
Object oTries = sct.get(KeyConstants._tries, null);
if (oTries == null) throw new ExpressionException("missing key tries inside struct");
if (oTries == null) throw new ExpressionException("Missing key tries inside struct");
int tries = Caster.toIntValue(oTries);
if (tries < 0) throw new ExpressionException("tries must contain a none negative value");
if (tries < 0) throw new ExpressionException("Tries must contain a none negative value");

// interval
Object oInterval = sct.get(KeyConstants._interval, null);
if (oInterval == null) oInterval = sct.get(KeyConstants._intervall, null);

if (oInterval == null) throw new ExpressionException("missing key interval inside struct");
if (oInterval == null) throw new ExpressionException("Missing key interval inside struct");
int interval = toSeconds(oInterval);
if (interval < 0) throw new ExpressionException("interval should contain a positive value or 0");
if (interval < 0) throw new ExpressionException("Interval should contain a positive value or 0");

return new ExecutionPlanImpl(tries + plus, interval);
}
Expand Down Expand Up @@ -306,7 +318,7 @@ public String _register(Page currentPage, int threadIndex) throws PageException
Threads ts = ThreadTag.getThreadScope(pc, name); // pc.getThreadScope(name);

if (type == TYPE_DAEMON) {
if (ts != null) throw new ApplicationException("could not create a thread with the name [" + name.getString() + "]. name must be unique within a request");
if (ts != null) throw new ApplicationException("Could not create a thread with the name [" + name.getString() + "]. name must be unique within a request");
ChildThreadImpl ct = new ChildThreadImpl((PageContextImpl) pc, currentPage, name.getString(), threadIndex, attrs, false, separateScopes);
ThreadsImpl t = new ThreadsImpl(ct);
PageContextImpl root = (PageContextImpl) getRootPageContext(pc);
Expand Down Expand Up @@ -410,7 +422,7 @@ private void doSleep() throws ExpressionException {

}

private void doJoin() throws ApplicationException {
private void doJoin() throws ApplicationException, PageException {
List<String> all = null, names;
Key name = name(false);
if (name == null) {
Expand All @@ -423,6 +435,7 @@ private void doJoin() throws ApplicationException {

Iterator<String> it = names.iterator();
String n;
PageException threadError = null;
while (it.hasNext()) {
n = it.next();
if (StringUtil.isEmpty(n, true)) continue;
Expand All @@ -431,7 +444,7 @@ private void doJoin() throws ApplicationException {
if (ts == null) {
if (all == null) all = ThreadTag.getTagNames(ThreadTag.getAllNoneAncestorThreads(pc));

throw new ApplicationException("there is no thread running with the name [" + n + "], " + "only the following threads existing [" + ListUtil.listToListEL(all, ", ")
throw new ApplicationException("There is no thread running with the name [" + n + "], " + "only the following threads existing [" + ListUtil.listToListEL(all, ", ")
+ "] ->" + ListUtil.toList(all, ", "));
}
ct = ts.getChildThread();
Expand All @@ -444,19 +457,24 @@ private void doJoin() throws ApplicationException {
catch (InterruptedException e) {
}
}
if (throwonerror && threadError == null && ts.containsKey(KeyConstants._error) ){
threadError = lucee.runtime.tag.Throw.toPageException(ts.get(KeyConstants._error), null );
}
if (_timeout != -1) {
_timeout = _timeout - (System.currentTimeMillis() - start);
if (_timeout < 1) break;
}
}
if (throwonerror && threadError != null) throw threadError;

}

private void doTerminate() throws ApplicationException {
// PageContextImpl mpc=(PageContextImpl)getMainPageContext(pc);

Threads ts = ThreadTag.getThreadScope(pc, KeyImpl.init(nameAsString(false))); // , ThreadTag.LEVEL_CURRENT + ThreadTag.LEVEL_KIDS

if (ts == null) throw new ApplicationException("there is no thread running with the name [" + nameAsString(false) + "]");
if (ts == null) throw new ApplicationException("There is no thread running with the name [" + nameAsString(false) + "]");
ChildThread ct = ts.getChildThread();

if (ct.isAlive()) {
Expand Down
12 changes: 12 additions & 0 deletions core/src/main/java/resource/tld/core-base.tld
Original file line number Diff line number Diff line change
Expand Up @@ -6143,6 +6143,18 @@ If you terminate a thread, the thread scope includes an ERROR metadata structure
page thread, the page continues waiting until the threads are joined,
even if you specify a page timeout. (optional, default=0)</description>
</attribute>
<attribute>
<type>boolean</type>
<name>throwOnError</name>
<required>false</required>
<default-value>false</default-value>
<rtexprvalue>true</rtexprvalue>
<description>Only for action="join"
Boolean value specifying whether to throw an
exception if any of the joined thread(s) have errored,
only the first error found will be thrown.
Default: false</description>
</attribute>
</tag>


Expand Down
48 changes: 48 additions & 0 deletions test/tickets/LDEV5152.cfc
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
component extends="org.lucee.cfml.test.LuceeTestCase" labels="thread" {

function testThreadJoinThrowOnError(){
var names = [];
loop from=1 to=2 index="local.i" {
ArrayAppend( names, "ldev5152_#i#" );
thread name="ldev5152_#i#" {
if ( thread.name eq "ldev5152_2" )
throw thread.name;
}
}

var err = {};
try {
thread action="join" name="#names.toList()#" throwOnError="true";
} catch ( e ){
err = e;
}

expect( err ).notToBeEmpty();
expect( err ).toHaveKey( "message" );
expect( err.message ).toInclude( "ldev5152_2" );

}

function testThreadNotJoinThrowOnError(){
var err = {};
try {
var names = [];
loop from=3 to=4 index="local.i" {
ArrayAppend( names, "ldev5152_#i#" );
thread name="ldev5152_#i#" throwOnError="true" {
if ( thread.name eq "ldev5152_3" )
throw thread.name;
}
}
} catch ( e ){
err = e;
}

expect( err ).notToBeEmpty();
expect( err ).toHaveKey( "message" );
expect( err.message ).toInclude( "Attribute [throwonerror] is only supported for action [join]" );

}


}
Loading