More robust Simantics platform shutdown logic. 69/1269/3
authorTuukka Lehtonen <tuukka.lehtonen@semantum.fi>
Wed, 29 Nov 2017 11:07:05 +0000 (13:07 +0200)
committerTuukka Lehtonen <tuukka.lehtonen@semantum.fi>
Wed, 29 Nov 2017 20:56:57 +0000 (22:56 +0200)
Added a JVM runtime shutdown hook to ensure that unless the JVM process
is killed forcibly, the JVM should attempt properly shutting down the
Simantics platform, even if the client code that started the platform
fails to do so for any reason.

Also marked both SimanticsPlatform.{startUp,shutdown} synchronized to
prevent concurrent access to either the start-up or shutdown logic.

refs #7650

Change-Id: I8c8022730ed973d80897fb364592881425b0a51f

bundles/org.simantics/src/org/simantics/SimanticsPlatform.java

index 961dfc71899d8a872d8941dda396f95a8ab2f0ca..91c34f0c788f59f4a35984aa764d184ad2d95f9f 100644 (file)
@@ -49,8 +49,6 @@ import org.eclipse.core.runtime.SubMonitor;
 import org.eclipse.osgi.service.resolver.BundleDescription;
 import org.ini4j.Ini;
 import org.ini4j.InvalidFileFormatException;
-import org.simantics.SimanticsPlatform.OntologyRecoveryPolicy;
-import org.simantics.SimanticsPlatform.RecoveryPolicy;
 import org.simantics.databoard.Bindings;
 import org.simantics.databoard.Databoard;
 import org.simantics.datatypes.literal.Font;
@@ -194,6 +192,19 @@ public class SimanticsPlatform implements LifecycleListener {
 
     public Thread mainThread;
 
+    private Thread shutdownHook = new Thread() {
+        @Override
+        public void run() {
+            try {
+                LOGGER.warn("Simantics platform was not properly shut down. Executing safety shutdown hook.");
+                shutdown(null, false);
+            } catch (PlatformException e) {
+                LOGGER.error("Simantics Platform shutdown hook execution failed.", e);
+                log.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Simantics Platform shutdown hook execution failed.", e));
+            }
+        }
+    };
+
     /**
      * The {@link IProject} activated by
      * {@link #startUp(IProgressMonitor, RecoveryPolicy, OntologyRecoveryPolicy, ServerAddress, PlatformUserAgent)}
@@ -570,31 +581,9 @@ public class SimanticsPlatform implements LifecycleListener {
             Set<GroupReference> publishedFeatureGroups = ProjectFeatures.getInstallGroupsOfPublishedFeatures();
             Collection<GroupReference> groupsWithoutVersion = GroupReference.stripVersions(publishedFeatureGroups);
 
-    //        final List<String> Platform_Features = new ArrayList<String>();
-    //
-    //        // Convert graph instances
-    //        Collection<TransferableGraph1> platformGraphs = new ArrayList<TransferableGraph1>();
-    //        for (GraphBundleEx e : platformTGs.values()) platformGraphs.add( e.getGraph() );
-    //        IGraph graph = Graphs.createGraph(platformGraphs);
-    //
-    //        Res    PublishedProjectFeatures = UriUtils.uriToPath( ProjectResource.URIs.PublishedProjectFeatures );
-    //        Path   HasFeature = UriUtils.uriToPath( ProjectResource.URIs.HasFeature );
-    //        for(Res feature : graph.getObjects(PublishedProjectFeatures, HasFeature)) {
-    //            System.out.println("Installing Project Feature: "+feature.toString());
-    //            Platform_Features.add( feature.toString() );
-    //        }
-
             try {
                 Transaction.startTransaction(session, true);
                 try {
-    //                for (String feature : Platform_Features) {
-    //                    try {
-    //                        getResource(feature);
-    //                    } catch(ResourceNotFoundException e) {
-    //                        System.out.println(feature+" not found");
-    //                    }
-    //                    mgmt.installFeature(projectResource, feature);
-    //                }
                     Projects.setProjectInstalledGroups(writeGraph(), projectResource, groupsWithoutVersion);
                     Transaction.commit();
                 } finally {
@@ -792,7 +781,8 @@ public class SimanticsPlatform implements LifecycleListener {
      * installing project features.
      * <p>
      *
-     * In SWB this is handled in SimanticsWorkbenchAdvisor#openWindows().
+     * In Simantics Workbench this is handled in
+     * <code>SimanticsWorkbenchAdvisor#openWindows()</code>.
      * <p>
      *
      * If remote server is given, simantics plaform takes connection there
@@ -806,7 +796,7 @@ public class SimanticsPlatform implements LifecycleListener {
      *        startup or <code>null</code> to resort to default measures
      * @throws PlatformException
      */
-    public SessionContext startUp(String databaseDriverId, IProgressMonitor progressMonitor, RecoveryPolicy workspacePolicy,
+    public synchronized SessionContext startUp(String databaseDriverId, IProgressMonitor progressMonitor, RecoveryPolicy workspacePolicy,
             OntologyRecoveryPolicy ontologyPolicy, boolean requireSynchronize, PlatformUserAgent userAgent)
     throws PlatformException
     {
@@ -936,6 +926,9 @@ public class SimanticsPlatform implements LifecycleListener {
 
         running = true;
 
+        // #7650: improve shutdown robustness in all applications that use the platform
+        Runtime.getRuntime().addShutdownHook(shutdownHook);
+
         return sessionContext;
 
     }
@@ -955,79 +948,31 @@ public class SimanticsPlatform implements LifecycleListener {
         }
     }
 
-//    private static File getIgnorePrerequisitesFile(URL workspaceUrl) {
-//        if (workspaceUrl == null)
-//            return null;
-//        return new File(workspaceUrl.getPath(), ".ignorePrerequisites");
-//    }
-//
-//    private void ensurePrerequisites(IProgressMonitor progressMonitor, PlatformUserAgent userAgent) throws PlatformException {
-//        Location loc = Platform.getInstanceLocation();
-//        File ignorePrerequisites = getIgnorePrerequisitesFile(loc.getURL());
-//        if (loc.isSet() && ignorePrerequisites != null) {
-//            if (ignorePrerequisites.exists() || ignorePrerequisites.isFile())
-//                return;
-//        }
-//
-//        try {
-//            ServerEnvironment.ensureServerDependenciesMet();
-//        } catch (ExecutionEnvironmentException e) {
-//            // Not installed properly, ask user whether to try installation.
-//            try {
-//                StringBuilder msg = new StringBuilder();
-//                msg.append("Your system seems to be missing the following prerequisites for running this application:\n\n");
-//                for (Product product : e.requiredProducts)
-//                    msg.append("\t" + product.getDescription() + "\n");
-//                msg.append("\nYou can either install the missing components now or ignore and attempt to start the application without them. Ignore Always will ignore this question for this workspace.");
-//                msg.append("\n\nSelecting Cancel will close the application.");
-//
-//                int selection = 0;
-//                if (userAgent != null) {
-//                    selection = userAgent.showPrompt("Missing Prerequisites", msg.toString(), new String[] {
-//                        "Install Pre-requisites",
-//                        "Ignore Now",
-//                        "Ignore Always",
-//                        "Cancel"
-//                    }, selection);
-//                }
-//                boolean tryInstall = false;
-//                switch (selection) {
-//                    case 0:
-//                        tryInstall = true;
-//                        break;
-//                    case 2:
-//                        ignorePrerequisites.createNewFile();
-//                    case 1:
-//                        break;
-//                    case 3:
-//                    case -1:
-//                        throw new CancelStartupException();
-//                }
-//
-//                if (tryInstall) {
-//                    // Try to install it and check for success afterwards.
-//                    ServerEnvironment.tryInstallDependencies(progressMonitor);
-//                    ServerEnvironment.ensureServerDependenciesMet();
-//                }
-//            } catch (InstallException ie) {
-//                throw new PlatformException(ie);
-//            } catch (ExecutionEnvironmentException eee) {
-//                throw new PlatformException(eee);
-//            } catch (IOException ie) {
-//                throw new PlatformException(ie);
-//            }
-//        }
-//    }
+    /**
+     * Perform normal shutdown for the Simantics Platform.
+     *
+     * @param progressMonitor optional progress monitor
+     * @throws PlatformException
+     * @see {@link #shutdown(IProgressMonitor, boolean)}
+     */
+    public synchronized void shutdown(IProgressMonitor progressMonitor) throws PlatformException {
+        shutdown(progressMonitor, true);
+    }
 
     /**
      * Shutdown Simantics Platform.
      *
-     * In SWB this is handled in SimanticsWorkbenchAdvisor#disconnectFromWorkspace.
+     * In Simantics Workbench this is handled in
+     * <code>SimanticsWorkbenchAdvisor#disconnectFromWorkspace</code>.
      *
-     * @param progressMonitor optional progress monitor
+     * @param progressMonitor
+     *            optional progress monitor
+     * @param clearTemporaryFiles
+     *            allow or prevent deletion of temporary files at the end of the
+     *            shutdown procedure
      * @throws PlatformException
      */
-    public void shutdown(IProgressMonitor progressMonitor) throws PlatformException
+    public synchronized void shutdown(IProgressMonitor progressMonitor, boolean clearTemporaryFiles) throws PlatformException
     {
         SubMonitor progress = SubMonitor.convert(progressMonitor, 100);
         PlatformException platformException = null;
@@ -1096,15 +1041,20 @@ public class SimanticsPlatform implements LifecycleListener {
         }
         progress.worked(10);
 
-        progress.subTask("Clearing Workspace Temporary Directory");
-        try {
-            Simantics.clearTemporaryDirectory();
-        } catch (Throwable t) {
-            LOGGER.error("Failed to clear the temporary directory.", t);
+        if (clearTemporaryFiles) {
+            progress.subTask("Clearing Workspace Temporary Directory");
+            try {
+                Simantics.clearTemporaryDirectory();
+            } catch (Throwable t) {
+                LOGGER.error("Failed to clear the temporary directory.", t);
+            }
         }
         progress.worked(10);
         if (null != platformException)
             throw platformException;
+
+        // #7650: improve shutdown robustness in all applications that use the platform
+        Runtime.getRuntime().removeShutdownHook(shutdownHook);
     }
 
     // TODO: consider removing this in the future ??