diff --git a/core/src/main/java/lucee/runtime/PageContextImpl.java b/core/src/main/java/lucee/runtime/PageContextImpl.java
index 11bfdef0f6..407e4acf42 100644
--- a/core/src/main/java/lucee/runtime/PageContextImpl.java
+++ b/core/src/main/java/lucee/runtime/PageContextImpl.java
@@ -792,7 +792,8 @@ private boolean lastStanding() {
while ((p = p.getParentPageContext()) != null) {
if (p.getStartTime() > 0) return false;
}
-
+ if (getParentPageContext() == null
+ && (tmp != null && tmp.size() == 0 )) return true; // TODO nested children?
return false;
}
diff --git a/core/src/main/java/lucee/runtime/functions/system/SessionCommit.java b/core/src/main/java/lucee/runtime/functions/system/SessionCommit.java
new file mode 100644
index 0000000000..13750b0f9c
--- /dev/null
+++ b/core/src/main/java/lucee/runtime/functions/system/SessionCommit.java
@@ -0,0 +1,15 @@
+package lucee.runtime.functions.system;
+
+import lucee.runtime.PageContext;
+import lucee.runtime.PageContextImpl;
+import lucee.runtime.exp.PageException;
+import lucee.runtime.ext.function.Function;
+
+public class SessionCommit implements Function {
+ private static final long serialVersionUID = -2243745577257724777L;
+
+ public static String call(PageContext pc) throws PageException {
+ ((PageContextImpl) pc).sessionScope().touchAfterRequest(pc);
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/lucee/runtime/type/scope/storage/IKHandlerCache.java b/core/src/main/java/lucee/runtime/type/scope/storage/IKHandlerCache.java
index 34c48dd278..78e9b59c5c 100644
--- a/core/src/main/java/lucee/runtime/type/scope/storage/IKHandlerCache.java
+++ b/core/src/main/java/lucee/runtime/type/scope/storage/IKHandlerCache.java
@@ -36,16 +36,16 @@ public IKStorageValue loadData(PageContext pc, String appName, String name, Stri
Object val = cache.getValue(key, null);
if (val instanceof byte[][]) {
ScopeContext.info(log,
- "load existing data from cache [" + name + "] to create " + strType + " scope for " + pc.getApplicationContext().getName() + "/" + pc.getCFID());
+ "Load existing byte data from cache [" + name + "] to create " + strType + " scope for " + pc.getApplicationContext().getName() + "/" + pc.getCFID());
return new IKStorageValue((byte[][]) val);
}
else if (val instanceof IKStorageValue) {
ScopeContext.info(log,
- "load existing data from cache [" + name + "] to create " + strType + " scope for " + pc.getApplicationContext().getName() + "/" + pc.getCFID());
+ "Load existing data from cache [" + name + "] to create " + strType + " scope for " + pc.getApplicationContext().getName() + "/" + pc.getCFID());
return (IKStorageValue) val;
}
else {
- ScopeContext.info(log, "create new " + strType + " scope for " + pc.getApplicationContext().getName() + "/" + pc.getCFID() + " in cache [" + name + "]");
+ ScopeContext.info(log, "Create new [" + strType + "] scope for [" + pc.getApplicationContext().getName() + "/" + pc.getCFID() + "] in cache [" + name + "]");
}
return null;
}
@@ -70,6 +70,7 @@ else if (existingVal != null) {
cache.remove(key);
}
}
+ ScopeContext.info(log, "Store scope for [" + pc.getApplicationContext().getName() + "/" + pc.getCFID() + "] in cache [" + name + "]");
}
catch (Exception e) {
ScopeContext.error(log, e);
diff --git a/core/src/main/java/resource/fld/core-base.fld b/core/src/main/java/resource/fld/core-base.fld
index 6801064a3d..fb20d83c6d 100755
--- a/core/src/main/java/resource/fld/core-base.fld
+++ b/core/src/main/java/resource/fld/core-base.fld
@@ -12646,7 +12646,16 @@ You can find a list of all available timezones in the Lucee administrator (Setti
string
-
+
+
+
+ SessionCommit
+ lucee.runtime.functions.system.SessionCommit
+ Force saving the session to storage, useful when sessionCluster is enabled.
+
+ void
+
+
diff --git a/test/tickets/LDEV2135.cfc b/test/tickets/LDEV2135.cfc
index 9b26bb5862..95c16578fb 100644
--- a/test/tickets/LDEV2135.cfc
+++ b/test/tickets/LDEV2135.cfc
@@ -2,6 +2,9 @@ component extends="org.lucee.cfml.test.LuceeTestCase" labels="session" {
function run( testResults , testBox ) {
describe( "Test suite for LDEV-2135 using memory", function() {
+
+ //beforeEach(function (currentSpec, data){ _beforeEach(currentSpec, data); });
+
it( title='thread looses session variables - sessionCluster=false', body=function( currentSpec ) {
test( {sessionCluster: false, sessionStorage: "memory"} );
});
@@ -12,41 +15,52 @@ component extends="org.lucee.cfml.test.LuceeTestCase" labels="session" {
});
describe( title="Test suite for LDEV-2135 using redis", skip=skipRedis(), body=function() {
+
+ //beforeEach(function (currentSpec, data){ _beforeEach(currentSpec, data); });
+
it( title='thread looses session variables - redis- sessionCluster=false', body=function( currentSpec ) {
test( {sessionCluster: false, sessionStorage: "redis"} );
});
- it( title='thread looses session variables - redis - sessionCluster=true', skip=true, body=function( currentSpec ) {
+ it( title='thread looses session variables - redis - sessionCluster=true', body=function( currentSpec ) {
test( {sessionCluster: true, sessionStorage: "redis"} );
});
+
+ it( title='thread looses session topLevel variables - redis - sessionCluster=true', body=function( currentSpec ) {
+ test( {sessionCluster: true, sessionStorage: "redis"}, "TopLevel" );
+ });
});
describe( title="Test suite for LDEV-2135 using memcached", skip=skipMemcached(), body=function() {
+
+ //beforeEach(function (currentSpec, data){ _beforeEach(currentSpec, data); });
+
it( title='thread looses session variables - memcached -sessionCluster=false', body=function( currentSpec ) {
test( {sessionCluster: false, sessionStorage: "memcached"} );
});
- it( title='thread looses session variables - memcached -sessionCluster=true', skip=true, body=function( currentSpec ) {
+ it( title='thread looses session variables - memcached -sessionCluster=true', body=function( currentSpec ) {
test( {sessionCluster: true, sessionStorage: "memcached"} );
});
});
}
- private function test( args ){
+ private function test( args, string template="" ){
var uri = createURI( "LDEV2135" );
var first = _InternalRequest(
- template : "#uri#/cfml-session/testThreadSession.cfm",
+ template : "#uri#/cfml-session/testThreadSession#template#.cfm",
url: args
);
+ systemOutput(args, true);
checkSess( first.fileContent );
var cookies = {
cfid: first.session.cfid,
cftoken: first.session.cftoken
};
-
+ systemOutput("-- before secondRequest.cfm", true);
var second = _InternalRequest(
- template : "#uri#/cfml-session/secondRequest.cfm",
+ template : "#uri#/cfml-session/secondRequest#template#.cfm",
url: args,
cookies: cookies
);
@@ -63,10 +77,10 @@ component extends="org.lucee.cfml.test.LuceeTestCase" labels="session" {
expect( s.threads ).toHaveLength( 5 );
expect( s ).toHaveKey( "afterJoin" );
}
-
- private string function createURI(string calledName){
- var baseURI = "/test/#listLast(getDirectoryFromPath(getCurrentTemplatePath()),"\/")#/";
- return baseURI&""&calledName;
+ private string function createURI(string calledName, boolean contract=true){
+ var base = getDirectoryFromPath( getCurrentTemplatePath() );
+ var baseURI = contract ? contractPath( base ) : "/test/#listLast(base,"\/")#";
+ return baseURI & "/" & calledName;
}
private function skipRedis(){
@@ -76,4 +90,9 @@ component extends="org.lucee.cfml.test.LuceeTestCase" labels="session" {
private function skipMemcached(){
return (structCount(server.getTestService( "memcached" )) eq 0);
}
+
+ private function _beforeEach(currentSpec, data){
+ systemOutput("", true);
+ systemOutput(currentspec, true);
+ }
}
\ No newline at end of file
diff --git a/test/tickets/LDEV2135/cfml-session/secondRequest.cfm b/test/tickets/LDEV2135/cfml-session/secondRequest.cfm
index 3613e86033..4dce77cfc4 100644
--- a/test/tickets/LDEV2135/cfml-session/secondRequest.cfm
+++ b/test/tickets/LDEV2135/cfml-session/secondRequest.cfm
@@ -1,4 +1,8 @@
- //systemOutput(session.toJson(), true);
+ if (!structKeyExists(session, "ldev3125")){
+ //systemOutput(session.toJson(), true);
+ throw "key session.ldev3125 missing";
+ }
+ //systemOutput(session.ldev3125.toJson(), true);
echo(session.ldev3125.toJson());
\ No newline at end of file
diff --git a/test/tickets/LDEV2135/cfml-session/secondRequestTopLevel.cfm b/test/tickets/LDEV2135/cfml-session/secondRequestTopLevel.cfm
new file mode 100644
index 0000000000..a613387df5
--- /dev/null
+++ b/test/tickets/LDEV2135/cfml-session/secondRequestTopLevel.cfm
@@ -0,0 +1,3 @@
+
+ echo(session.toJson());
+
\ No newline at end of file
diff --git a/test/tickets/LDEV2135/cfml-session/testThreadSession.cfm b/test/tickets/LDEV2135/cfml-session/testThreadSession.cfm
index b6065d5dd6..bac5081df5 100644
--- a/test/tickets/LDEV2135/cfml-session/testThreadSession.cfm
+++ b/test/tickets/LDEV2135/cfml-session/testThreadSession.cfm
@@ -1,4 +1,5 @@
+ //systemOutput(url.toJson(), true);
session.ldev3125 = {};
session.ldev3125.sessionCluster = url.sessionCluster;
session.ldev3125.start = 'survived';
@@ -13,15 +14,18 @@
thread name="#name#" {
try {
ArrayAppend( session.ldev3125.threads, thread.name );
- sleep( 10 );
+ // sleep( 10 ); // might need to be 1000
throw(type="blah", message="boom");
} catch(any e) {
- writedump(session.ldev3125);
+ dump(session.ldev3125);
}
}
}
-
+ session.ldev3125.beforeJoin = 'hello';
thread action="join" name="#_threads.toList()#";
session.ldev3125.afterJoin = 'goodbye';
echo( session.ldev3125.toJson() );
+ //systemOutput(session.toJson(), true);
+ //session.topLevel = true;
+ //sessionCommit();
\ No newline at end of file
diff --git a/test/tickets/LDEV2135/cfml-session/testThreadSessionTopLevel.cfm b/test/tickets/LDEV2135/cfml-session/testThreadSessionTopLevel.cfm
new file mode 100644
index 0000000000..440e08fc18
--- /dev/null
+++ b/test/tickets/LDEV2135/cfml-session/testThreadSessionTopLevel.cfm
@@ -0,0 +1,30 @@
+
+ //systemOutput(url.toJson(), true);
+ session.sessionCluster = url.sessionCluster;
+ session.start = 'survived';
+ session.threads = [];
+ session.sessionStorage = url.sessionStorage;
+
+ _threads = [];
+
+ for(i in [ 1, 2, 3, 4, 5 ] ) {
+ name = "ldev2135-#i#";
+ arrayAppend( _threads, name);
+ thread name="#name#" {
+ try {
+ ArrayAppend( session.threads, thread.name );
+ // sleep( 10 ); // might need to be 1000
+ throw(type="blah", message="boom");
+ } catch(any e) {
+ dump(session);
+ }
+ }
+ }
+ session.beforeJoin = 'hello';
+ thread action="join" name="#_threads.toList()#";
+ session.afterJoin = 'goodbye';
+ echo( session.toJson() );
+ //systemOutput(session.toJson(), true);
+ session.topLevel = true;
+ //sessionCommit();
+
\ No newline at end of file