From 6b56e05ab09383e35e3863bb2548ceb474168bcf Mon Sep 17 00:00:00 2001 From: Tim Wagner Date: Thu, 28 Apr 2016 17:20:07 +0200 Subject: [PATCH 1/4] Refactoring method invocation process on session beans --- resources/schema/appserver.xsd | 20 +++++ .../PersistenceContainer/BeanManager.php | 81 ++++++++++++++++++- .../PersistenceContainerValve.php | 22 ++--- 3 files changed, 102 insertions(+), 21 deletions(-) diff --git a/resources/schema/appserver.xsd b/resources/schema/appserver.xsd index c939b1d85..5931e3355 100644 --- a/resources/schema/appserver.xsd +++ b/resources/schema/appserver.xsd @@ -1007,6 +1007,16 @@ + + + + + + + + + + @@ -1568,6 +1578,7 @@ + @@ -1778,6 +1789,15 @@ + + + + + + + + + diff --git a/src/AppserverIo/Appserver/PersistenceContainer/BeanManager.php b/src/AppserverIo/Appserver/PersistenceContainer/BeanManager.php index 971e421fe..dc8419773 100644 --- a/src/AppserverIo/Appserver/PersistenceContainer/BeanManager.php +++ b/src/AppserverIo/Appserver/PersistenceContainer/BeanManager.php @@ -21,8 +21,10 @@ namespace AppserverIo\Appserver\PersistenceContainer; -use AppserverIo\Appserver\Core\AbstractEpbManager; +use AppserverIo\Collections\CollectionUtils; +use AppserverIo\Collections\CollectionInterface; use AppserverIo\Storage\StorageInterface; +use AppserverIo\Appserver\Core\AbstractEpbManager; use AppserverIo\Lang\Reflection\AnnotationInterface; use AppserverIo\Psr\Application\ApplicationInterface; use AppserverIo\Psr\EnterpriseBeans\BeanContextInterface; @@ -34,11 +36,14 @@ use AppserverIo\Psr\EnterpriseBeans\Description\StatefulSessionBeanDescriptorInterface; use AppserverIo\Psr\EnterpriseBeans\Description\StatelessSessionBeanDescriptorInterface; use AppserverIo\Psr\EnterpriseBeans\Description\MessageDrivenBeanDescriptorInterface; +use AppserverIo\Appserver\Application\Interfaces\ManagerSettingsInterface; use AppserverIo\Appserver\Application\Interfaces\ManagerSettingsAwareInterface; use AppserverIo\Appserver\PersistenceContainer\Utils\SessionBeanUtil; use AppserverIo\Appserver\PersistenceContainer\DependencyInjection\DirectoryParser; use AppserverIo\Appserver\PersistenceContainer\DependencyInjection\DeploymentDescriptorParser; -use AppserverIo\Appserver\Application\Interfaces\ManagerSettingsInterface; +use AppserverIo\Appserver\DependencyInjectionContainer\Interfaces\ObjectManagerInterface; +use AppserverIo\RemoteMethodInvocation\RemoteMethodInterface; +use AppserverIo\RemoteMethodInvocation\FilterSessionPredicate; /** * The bean manager handles the message and session beans registered for the application. @@ -443,7 +448,7 @@ public function destroyBeanInstance($instance) { // load the object manager - $objectManager = $this->getApplication()->search('ObjectManagerInterface'); + $objectManager = $this->getApplication()->search(ObjectManagerInterface::IDENTIFIER); // load the bean descriptor $descriptor = $objectManager->getObjectDescriptors()->get(get_class($instance)); @@ -456,6 +461,74 @@ public function destroyBeanInstance($instance) } } + /** + * Invoke the passed remote method on the described session bean and return the result. + * + * @param \AppserverIo\RemoteMethodInvocation\RemoteMethodInterface $remoteMethod The remote method description + * @param \AppserverIo\Collections\CollectionInterface $sessions The collection with the sessions + * + * @return mixed The result of the remote method invocation + */ + public function invoke(RemoteMethodInterface $remoteMethod, CollectionInterface $sessions) + { + + // prepare method name and parameters and invoke method + $className = $remoteMethod->getClassName(); + $methodName = $remoteMethod->getMethodName(); + $parameters = $remoteMethod->getParameters(); + $sessionId = $remoteMethod->getSessionId(); + + // load the application instance + $application = $this->getApplication(); + + // try to load the session with the ID passed in the remote method + $session = CollectionUtils::find($sessions, new FilterSessionPredicate($sessionId)); + + // query whether the session is available or not + if ($session instanceof CollectionInterface) { + // query whether we already have an instance in the session container + if ($instance = $session->exists($className)) { + $instance = $session->get($className); + } + } + + // load a fresh bean instance and add it to the session container + if ($instance == null) { + $instance = $application->search($className, array($sessionId, array($application))); + $session->add($className, $instance); + } + + // invoke the remote method call on the local instance + $response = call_user_func_array(array($instance, $methodName), $parameters); + + // load the object manager + $objectManager = $application->search(ObjectManagerInterface::IDENTIFIER); + + // load the bean descriptor + $descriptor = $objectManager->getObjectDescriptors()->get(get_class($instance)); + + // initialize the flag to mark the instance to be re-attached + $attach = true; + + // query if we've stateful session bean + if ($descriptor instanceof StatefulSessionBeanDescriptorInterface) { + // remove the SFSB instance if a remove method has been called + if ($descriptor->isRemoveMethod($methodName)) { + $this->removeStatefulSessionBean($sessionId, $className); + $session->remove($className); + $attach = false; + } + } + + // re-attach the bean instance if necessary + if ($attach === true) { + $this->attach($instance, $sessionId); + } + + // return the remote method call result + return $response; + } + /** * Attaches the passed bean, depending on it's type to the container. * @@ -469,7 +542,7 @@ public function attach($instance, $sessionId = null) { // load the object manager - $objectManager = $this->getApplication()->search('ObjectManagerInterface'); + $objectManager = $this->getApplication()->search(ObjectManagerInterface::IDENTIFIER); // load the bean descriptor $descriptor = $objectManager->getObjectDescriptors()->get(get_class($instance)); diff --git a/src/AppserverIo/Appserver/PersistenceContainer/PersistenceContainerValve.php b/src/AppserverIo/Appserver/PersistenceContainer/PersistenceContainerValve.php index cfce16e30..6ea49be20 100644 --- a/src/AppserverIo/Appserver/PersistenceContainer/PersistenceContainerValve.php +++ b/src/AppserverIo/Appserver/PersistenceContainer/PersistenceContainerValve.php @@ -25,6 +25,8 @@ use AppserverIo\Psr\Servlet\Http\HttpServletResponseInterface; use AppserverIo\RemoteMethodInvocation\RemoteMethodProtocol; use AppserverIo\RemoteMethodInvocation\RemoteExceptionWrapper; +use AppserverIo\Collections\HashMap; +use AppserverIo\Psr\EnterpriseBeans\BeanContextInterface; /** * Valve implementation that will be executed by the servlet engine to handle @@ -59,29 +61,15 @@ public function invoke(HttpServletRequestInterface $servletRequest, HttpServletR /** @var \AppserverIo\Appserver\Application\Application $application */ $application = $servletRequest->getContext(); - // prepare method name and parameters and invoke method - $className = $remoteMethod->getClassName(); - $methodName = $remoteMethod->getMethodName(); - $parameters = $remoteMethod->getParameters(); - $sessionId = $remoteMethod->getSessionId(); - - // load the bean manager and the bean instance - $instance = $application->search($className, array($sessionId, array($application))); - - // invoke the remote method call on the local instance - $response = call_user_func_array(array($instance, $methodName), $parameters); + // invoke the remote method and re-attach the bean instance to the container + $response = $application->search(BeanContextInterface::IDENTIFIER)->invoke($remoteMethod, new HashMap()); // serialize the remote method and write it to the socket $servletResponse->appendBodyStream(RemoteMethodProtocol::pack($response)); - // re-attach the bean instance in the container and unlock it - $application->search('BeanContextInterface')->attach($instance, $sessionId); - } catch (\Exception $e) { // catch the exception and append it to the body stream - $servletResponse->appendBodyStream( - RemoteMethodProtocol::pack(RemoteExceptionWrapper::factory($e)) - ); + $servletResponse->appendBodyStream(RemoteMethodProtocol::pack(RemoteExceptionWrapper::factory($e))); } // finally dispatch this request, because we have finished processing it From 9d710c8e3f9d7fac1f0f75489a7b739568c4088d Mon Sep 17 00:00:00 2001 From: wagnert Date: Thu, 28 Apr 2016 22:36:43 +0200 Subject: [PATCH 2/4] Fixed memory leak when using invalid key to remove SFSBs from map --- .../PersistenceContainer/BeanManager.php | 2 +- .../DoctrineEntityManagerDecorator.php | 18 ++++++++++++++++-- .../StandardGarbageCollector.php | 17 ++++++++++++----- .../StatefulSessionBeanMap.php | 10 ++++------ 4 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/AppserverIo/Appserver/PersistenceContainer/BeanManager.php b/src/AppserverIo/Appserver/PersistenceContainer/BeanManager.php index dc8419773..c1f9245d8 100644 --- a/src/AppserverIo/Appserver/PersistenceContainer/BeanManager.php +++ b/src/AppserverIo/Appserver/PersistenceContainer/BeanManager.php @@ -514,7 +514,7 @@ public function invoke(RemoteMethodInterface $remoteMethod, CollectionInterface if ($descriptor instanceof StatefulSessionBeanDescriptorInterface) { // remove the SFSB instance if a remove method has been called if ($descriptor->isRemoveMethod($methodName)) { - $this->removeStatefulSessionBean($sessionId, $className); + $this->removeStatefulSessionBean($sessionId, $descriptor->getClassName()); $session->remove($className); $attach = false; } diff --git a/src/AppserverIo/Appserver/PersistenceContainer/Doctrine/DoctrineEntityManagerDecorator.php b/src/AppserverIo/Appserver/PersistenceContainer/Doctrine/DoctrineEntityManagerDecorator.php index ba58b3d28..e35b1e7a8 100644 --- a/src/AppserverIo/Appserver/PersistenceContainer/Doctrine/DoctrineEntityManagerDecorator.php +++ b/src/AppserverIo/Appserver/PersistenceContainer/Doctrine/DoctrineEntityManagerDecorator.php @@ -43,8 +43,10 @@ class DoctrineEntityManagerDecorator extends EntityManagerDecorator public function __sleep() { - // close the connection - $this->getWrapped()->getConnection()->close(); + // query whether we've a wrapped instance + if ($wrapped = $this->getWrapped()) { + $this->getWrapped()->getConnection()->close(); + } // we want to serialize NOTHING return array(); @@ -63,6 +65,18 @@ public function __destruct() } } + /** + * {@inheritdoc} + */ + public function getConnection() + { + + // query whether we've a wrapped instance + if ($wrapped = $this->getWrapped()) { + return $wrapped->getConnection(); + } + } + /** * Returns the wrapped entity manager instance. * diff --git a/src/AppserverIo/Appserver/PersistenceContainer/StandardGarbageCollector.php b/src/AppserverIo/Appserver/PersistenceContainer/StandardGarbageCollector.php index d46b9aaab..71e845d14 100644 --- a/src/AppserverIo/Appserver/PersistenceContainer/StandardGarbageCollector.php +++ b/src/AppserverIo/Appserver/PersistenceContainer/StandardGarbageCollector.php @@ -24,6 +24,7 @@ use AppserverIo\Appserver\Core\AbstractDaemonThread; use AppserverIo\Psr\Application\ApplicationInterface; use AppserverIo\Appserver\Naming\Utils\NamingDirectoryKeys; +use Psr\Log\LogLevel; /** * The garbage collector for the stateful session beans. @@ -120,19 +121,25 @@ public function collectGarbage() // initialize the timestamp with the actual time $actualTime = time(); + // load the map with the SFSB lifetime data + $lifetimeMap = $statefulSessionBeans->getLifetime(); + + // write a log message with size of SFSBs to be garbage collected + $this->log(LogLevel::DEBUG, sprintf('Found %d SFSBs be garbage collected', sizeof($lifetimeMap))); + // iterate over the applications sessions with stateful session beans - foreach ($statefulSessionBeans->getLifetime() as $identifier => $lifetime) { + foreach ($lifetimeMap as $identifier => $lifetime) { // check the lifetime of the stateful session beans if ($lifetime < $actualTime) { // if the stateful session bean has timed out, remove it $statefulSessionBeans->remove($identifier, array($beanManager, 'destroyBeanInstance')); // write a log message - $this->getApplication() - ->getNamingDirectory() - ->search(NamingDirectoryKeys::SYSTEM_LOGGER) - ->debug(sprintf('Successfully removed SFSB %s', $identifier)); + $this->log(LogLevel::DEBUG, sprintf('Successfully removed SFSB %s', $identifier)); // reduce CPU load usleep(1000); + } else { + // write a log message + $this->log(LogLevel::DEBUG, sprintf('Lifetime %s of SFSB %s is > %s', $lifetime, $identifier, $actualTime)); } } diff --git a/src/AppserverIo/Appserver/PersistenceContainer/StatefulSessionBeanMap.php b/src/AppserverIo/Appserver/PersistenceContainer/StatefulSessionBeanMap.php index 4b69f39da..1e8678147 100644 --- a/src/AppserverIo/Appserver/PersistenceContainer/StatefulSessionBeanMap.php +++ b/src/AppserverIo/Appserver/PersistenceContainer/StatefulSessionBeanMap.php @@ -215,12 +215,11 @@ public function remove($key, callable $beforeRemove = null) } // remove the item unset($this->items[$key]); - // return the lifetime is set + // remove the lifetime if set if (isset($this->lifetime[$key])) { unset($this->lifetime[$key]); } - // return the instance - return $this; + return; } else { throw new IndexOutOfBoundsException('Index ' . $key . ' out of bounds'); } @@ -247,12 +246,11 @@ public function remove($key, callable $beforeRemove = null) } // remove the item unset($this->items[$newKey]); - // return the lifetime is set + // remove the lifetime if set if (isset($this->lifetime[$newKey])) { unset($this->lifetime[$newKey]); } - // returns the instance - return $this; + return; } else { throw new IndexOutOfBoundsException('Index ' . $newKey . ' out of bounds'); } From a6b0a9ceadb7a40a7de225f1d6d38e8583ef2d9e Mon Sep 17 00:00:00 2001 From: Tim Wagner Date: Fri, 29 Apr 2016 11:15:50 +0200 Subject: [PATCH 3/4] Switch to description 6.0 release --- CHANGELOG.md | 3 ++- composer.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8787aa69..9cc307535 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,11 @@ ## Bugfixes -* None +* Closed [#859](https://github.com/appserver-io/appserver/issues/859) - Memory Leaks in Session Beans ## Features +* Add @Remove annotation to allow explicit desctruction of SFSBs * Closed [#940](https://github.com/appserver-io/appserver/issues/940) - Allow different environments aka stages with corresponding configuration # Version 1.1.1-beta9 diff --git a/composer.json b/composer.json index 05ff3c953..c7639eeac 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "appserver-io/single-app" : "~2.0", "appserver-io/properties" : "~2.0", "appserver-io/concurrency" : "0.3.*", - "appserver-io/description" : "~5.0", + "appserver-io/description" : "~6.0", "appserver-io/configuration" : "~2.0", "appserver-io/doppelgaenger" : "~1.0", "appserver-io/routlt-project" : "~1.0" From 3e4a270a05f7572128200f6fc0890872d435dd2b Mon Sep 17 00:00:00 2001 From: Tim Wagner Date: Fri, 29 Apr 2016 11:22:54 +0200 Subject: [PATCH 4/4] Fixed PSR-2 errors --- .../Doctrine/DoctrineEntityManagerDecorator.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/AppserverIo/Appserver/PersistenceContainer/Doctrine/DoctrineEntityManagerDecorator.php b/src/AppserverIo/Appserver/PersistenceContainer/Doctrine/DoctrineEntityManagerDecorator.php index e35b1e7a8..5a992eb67 100644 --- a/src/AppserverIo/Appserver/PersistenceContainer/Doctrine/DoctrineEntityManagerDecorator.php +++ b/src/AppserverIo/Appserver/PersistenceContainer/Doctrine/DoctrineEntityManagerDecorator.php @@ -45,7 +45,7 @@ public function __sleep() // query whether we've a wrapped instance if ($wrapped = $this->getWrapped()) { - $this->getWrapped()->getConnection()->close(); + $wrapped->getConnection()->close(); } // we want to serialize NOTHING @@ -66,7 +66,9 @@ public function __destruct() } /** - * {@inheritdoc} + * Returns the entity manager's connection instance. + * + * @return \Doctrine\DBAL\Connection The connection instance */ public function getConnection() {