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