Which hops does the uninstall request cross before it fails?
The failure below comes from an Ignition 7.7.5 gateway on Ubuntu, uninstalling the SDK example modulecom.inductiveautomation.examples.simpletagprovider. It reproduced on two separate attempts.
| Hop | Class / method in the trace | What happens there | Timestamp |
|---|---|---|---|
| 1. Operator request | ConfirmationPanel$1 |
Uninstall is confirmed in the Gateway web interface. The error is reported back here. | 16:03:30,766 (error surfaced) |
| 2. Message dispatch |
RedundancyManagerImpl.dispatchMessage → QueueableMessageReceiver.receiveCall
|
Module commands go through the gateway's queued message path. This hop appears in the trace whether or not redundancy is configured. | — |
| 3. Command execution |
ModuleManagerImpl$UninstallCommand.execute → uninstallModuleInternal
|
ModuleManager runs the uninstall operation. | — |
| 4. Module shutdown | Module's gateway hook shutdown()
|
The module's own cleanup code runs. The log shows it completing. | 16:03:30,605 ("completed in 6 ms") |
| 5. Listener fan-out |
notifyModuleStartedOrStopped → ScriptHintsManager$1.moduleStopped
|
The gateway tells subsystems that the module set changed. Script hints are rebuilt. | — |
| 6. Script manager rebuild |
SRContext.createScriptManager → ProjectManagerImpl.getGlobalProject → getProject
|
The rebuild loads the global project record. | — |
| 7. Internal config DB |
SSessionJdbc.find → DelegatingDataSource (localdb.hsql) → JDBCPreparedStatement.executeQuery
|
SELECT ... FROM PROJECTS WHERE PROJECTS_ID = ? for [ProjectRecord -1] is rolled back by HSQLDB. |
16:03:30,760 (ERROR) |
The request stops at hop 7. The damage happened earlier, at hop 4. The checks below trace the fault from hop 7 back to hop 4.
Check 1: Did the module's own shutdown() throw?
Reading: in the gateway wrapper log (lines prefixed INFO | jvm 1), find the ModuleManager line just before the ERROR.
| What the log shows | Meaning | Next step |
|---|---|---|
Shutdown of module "..." completed in N ms, then Error running "uninstall" operation with a stack through notifyModuleStartedOrStopped
|
No exception left shutdown(). The failure is a side effect that shows up in a later listener. |
Check 2 |
The module's own logger reports an error from shutdown() (for example, the example hook's Error stopping Gateway module.) |
The module's cleanup code threw. | Fix that exception first, then rerun Check 1 |
| No completion line for the module |
shutdown() hung or never returned to ModuleManager |
Take a thread dump and look for a blocked shutdown thread |
In this case the module reported shutdown completed in 6 ms at 16:03:30,605. A clean return fromshutdown() only proves that no exception escaped. It says nothing about what the method did to objects it received from the platform. That gap is why this fault is easy to misread as a gateway bug.
Check 2: What does the serialization failure on ProjectRecord -1 point to?
Reading: the deepest Caused by in the trace. Here it is org.hsqldb.HsqlException: transaction rollback: serialization failure. The JDBC layer wraps it as java.sql.SQLTransactionRollbackException, and simpleorm wraps it again as simpleorm.utils.SException$Jdbc.
The localdb.hsql.DelegatingDataSource frame shows this is the gateway's internal HSQLDB configuration store, not an external database connection. A serialization failure means the engine rolled back the statement because it conflicted with transaction state held by another session. The engine is refusing to serialize two transactions. This is not a missing-table error, a SQL syntax error, or a sign of a corrupt file.
The statement that failed is a single-row read of the global project, which is PROJECTS_ID = -1, reached through getGlobalProject. On a healthy gateway, a read of one configuration row has nothing to conflict with. So something else left the internal database with a transaction open or abandoned.
| Pattern | Interpretation | Next step |
|---|---|---|
| Happens on every uninstall of the same module, with no other config edits in progress | The module's shutdown leaves the gateway in a bad state | Check 3 |
| Happened once, at the same moment as a project save or another configuration write | A real concurrency collision in the config DB | Retry the operation. If it does not recur, stop here. |
| Serialization failures also appear during normal operation, with no module operations | A gateway-wide internal DB contention problem, unrelated to uninstall | Treat as a separate gateway fault |
Here the failure reproduced on two attempts and was only cleared by reinstalling the gateway. That rules out a one-off collision.
Check 3: Is the gateway in Emergency mode?
Reading: open the Licensing page in the Gateway web configuration and read the license state after the failed uninstall.
In this installation, the Licensing page showed Emergency mode active. Uninstalling a tag provider module has no reason to change licensing state. When licensing changes state at the same moment the internal config DB starts rejecting reads, the fault sits in infrastructure that both subsystems share. It is not in the module's own objects.
| Licensing state after the failed uninstall | Meaning | Next step |
|---|---|---|
| Emergency mode, which was not active before the uninstall | A shared gateway service was disabled during module shutdown | Check 4, and look for shared-service calls in shutdown()
|
| Normal, unchanged | The damage is limited to the config DB path | Check 4 still applies. Also look for module code that opens internal-DB sessions and never closes them. |
| Emergency mode that was already active before the uninstall | A licensing issue unrelated to this module | Resolve licensing separately. Continue with Check 4 for the uninstall error. |
Check 4: Does shutdown() stop a service the module does not own?
Reading: the module's gateway hook source. Search it for lifecycle calls made on objects obtained from the GatewayContext:
grep -rn "getExecutionManager().shutdown" src/
grep -rnE "context\.get[A-Za-z]+\(\)\.(shutdown|stop|close)\(" src/
In the failing module, shutdown() contained this sequence:
if (context.getExecutionManager() != null) {
context.getExecutionManager().shutdown();
}
ourProvider.shutdown();
After those ExecutionManager lines were commented out, the uninstall error could no longer be reproduced.
The rule is to find out who owns an object before you close it. The table below separates objects the module may shut down from objects it may only unregister from.
| Object | Owner | Correct action in the module's shutdown()
|
|---|---|---|
ExecutionManager returned by context.getExecutionManager()
|
Gateway platform, shared by all subsystems and modules | Call unRegister or unRegisterAll for this module's tasks only. Never call shutdown(). |
The module's tag provider instance (ourProvider) |
The module | Call shutdown() on it |
A thread pool or executor the module created itself with new
|
The module | Shut it down and wait for termination |
| Any other manager obtained from the context | Gateway platform | Remove only what the module registered with it |
If the search finds nothing, widen it. Look for any stop, close, or shutdown call on a context-supplied reference, including calls made inside helper classes that the hook invokes.
Why does one shutdown() call take down unrelated gateway functions?
context.getExecutionManager() returns the gateway's shared ExecutionManager. It is not a per-module instance. Gateway subsystems and every loaded module register their periodic work with the same manager. They all run on its scheduling threads.
Calling shutdown() on it stops the scheduler for every caller:
- In-flight tasks are interrupted. A task stopped partway through a unit of work can leave an internal-DB transaction open or abandoned.
- Scheduled tasks stop firing. Periodic housekeeping, including the work that keeps licensing state current, no longer runs.
- Later registrations have no working scheduler behind them. Subsystems that re-register work after the module-set change do not get it executed.
The first code path to touch the internal database after this is the ScriptHintsManager rebuild triggered by the module-stopped notification. It reads the global project, runs into the transaction state left behind, and HSQLDB rolls it back with a serialization failure. The operator sees the error as an uninstall failure. The actual fault is that the module shut down the platform's scheduler.
| Symptom | Where to read it | Link to the shared-executor shutdown |
|---|---|---|
Error running "uninstall" operation |
Wrapper log, ModuleManager
|
A post-shutdown listener fails. The module's own shutdown reported success. |
transaction rollback: serialization failure on [ProjectRecord -1]
|
Deepest Caused by in the trace |
An internal-DB read conflicts with transaction state left by interrupted work |
| Same error shown in the web UI |
ConfirmationPanel$1 log line |
The failure is passed back to the requesting page |
| Emergency mode on the Licensing page | Gateway config, Licensing | Shared gateway background work has stopped |
| Only a gateway reinstall clears the state | Operational history | A JVM restart builds a new ExecutionManager |
Why does reinstalling the gateway only appear to fix it?
A reinstall restarts the gateway JVM. At startup the platform creates a fresh ExecutionManager, and every subsystem registers its work again. The broken state disappears, but the module still contains the shutdown() call. The next uninstall, or any module restart that runs the hook's shutdown(), reproduces the fault. That is why it happened twice.
To recover a running gateway, restart the gateway service before considering a reinstall. A service restart rebuilds the same in-memory state that the reinstall rebuilt, and it leaves configuration and installed modules alone. Then use the Licensing page to decide the next step:
- If Licensing returns to normal after the restart, the Emergency mode was a side effect of the stopped executor.
- If Emergency mode persists after a clean restart, treat it as a licensing problem independent of this module.
Neither a restart nor a reinstall fixes the module. Only a code change does.
How should the module release its scheduled work?
Commenting out the ExecutionManager shutdown stops the damage, but it only fixes half the problem. The example module registers a value-update task. With the shutdown line removed and nothing put in its place, that task stays registered on the shared manager after uninstall. It keeps firing against a provider that has been shut down, and against classes from a module classloader that is being discarded. The correct change replaces the shutdown with an unregister.
- At registration in
startup(), use a fixed owner string for the module and a fixed name for each task. Keep both as constants soshutdown()can reference exactly the same keys. - In
shutdown(), unregister the module's tasks first. UseunRegisterfor each named task, orunRegisterAllfor everything registered under the module's owner string. Check the exact method signatures in the ExecutionManager Javadoc for the SDK version you build against. - Shut down the module-owned tag provider after its update task is unregistered. This way no task run can write into a provider that is shutting down.
- If the module needs its own thread pool, create a private executor in
startup()and shut down only that instance inshutdown(). - Delete every
getExecutionManager().shutdown()call, including commented-out copies, so no one restores it later.
The same ownership rule applies to every manager the GatewayContext hands out. Register and unregister your own entries, and leave the manager's lifecycle to the gateway.
How do you verify the fix end to end?
- Restart the gateway service so the test starts with a fresh shared ExecutionManager. Confirm that the Licensing page no longer shows Emergency mode. If it still does, fix licensing before continuing so the result is not ambiguous.
- Record a baseline. Note one piece of gateway background activity that does not belong to the module, such as another tag provider updating or a scheduled gateway script running. You will check it again after the uninstall.
- Build the corrected module and install it. Confirm that the simple tag provider's values update, which proves the value-update task is registered and running.
- Uninstall the module from the Gateway web interface.
- In the wrapper log, confirm that
Shutdown of module "com.inductiveautomation.examples.simpletagprovider" completedand the hook'sGateway module stopped.line both appear. Also confirm there is noERROR [ModuleManager]Error running "uninstall" operation, noSQLTransactionRollbackException, and noERROR [ConfirmationPanel$1]. - Reopen the Licensing page. The license state must match what it was before the uninstall.
- Confirm the module's task is gone. No log output or tag writes from the module's update task should appear after the uninstall timestamp.
- Confirm the baseline activity from step 2 is still updating. This shows the shared executor survived.
- Repeat the full install and uninstall cycle at least twice more, checking steps 5 through 8 each time. The original fault reproduced on two attempts, so a single clean pass does not prove the fix.
FAQ
What happens if an Ignition module calls ExecutionManager.shutdown() in its shutdown method?
It stops the gateway's shared ExecutionManager, not a private one. Gateway-wide scheduled work stops or is interrupted. The next internal-DB read, such as the global project lookup after a module stops, can fail with transaction rollback: serialization failure, and the gateway can drop into Emergency mode.
What happens if I remove the shutdown() call but never unregister my task?
The uninstall error goes away, but the module's task stays registered on the shared ExecutionManager. It keeps firing after the module and its provider are gone. Replace the shutdown with unRegister or unRegisterAll, using the same owner and task name strings as at registration.
What happens if I restart the Ignition gateway instead of reinstalling it?
A service restart rebuilds the JVM state, including a fresh ExecutionManager, so it clears the same condition a reinstall clears without touching configuration. The fault returns on the next uninstall until the module's shutdown() is corrected.
Why does "transaction rollback: serialization failure" appear on PROJECTS_ID -1 during a module uninstall?
After a module stops, ScriptHintsManager rebuilds the script manager and reads the global project, which is record -1, from the internal HSQLDB config store. HSQLDB rolls back that read because it conflicts with transaction state left by work interrupted when the shared executor was shut down. It is a concurrency rejection, not database corruption.