Writing a distributed startable plugin
A Distributed startable plugin runs on every server in a cluster. Guidewire strongly recommends that Distributed plugins save their current start/stop state in a database. Persisting the start/stop state enables the plugin to handle edge cases, such as a server joining the cluster after other servers have started. To keep the state consistent across all servers, the state must persist in the database. Custom code can be written to persist the plugin's start/stop state.
The events related to servers and startable plugins are described in the following scenarios.
- The server starts. When the appropriate server run level is reached, the startable plugin's
start method is invoked with the serverStartup argument set to
true. - Operations enacted through the user interface or an invoked API can result in a request being sent to a server
to start or stop a particular startable plugin. The server processes the request by calling the plugin's
start or stop method. In these cases, the start
method's serverStartup argument is set to
false. Similarly, the stop method's serverStopping argument is also set tofalse. - The server shuts down. The startable plugin's stop method is invoked with the
serverStopping argument set to
true.
The plugin's start method accepts a handler argument of the type
StartablePluginCallbackHandler. The methods implemented in the handler manage the database
state information.
getState– Retrieves the current start/stop state from the database. Returnstrueif the server has started, otherwisefalse. The first time the plugin is run on a server, the database state field does not yet exist. To handle this situation, the method accepts a single argument that specifies the default state to use to initialize the field. If the database field has been set by earlier operations, the argument is ignored.setState– Sets the plugin's start/stop state in the database. The method accepts a single argument that specifies the current state:trueif running, otherwisefalse.logStart– Logs the event of starting the plugin. The method accepts a single argument that specifies the text describing the event.logStop– Logs the event of stopping the plugin. The method accepts a single argument that specifies the text describing the event.
The following Gosu example code implements start and stop methods for a Distributed startable plugin. It creates a trivial thread and maintains the plugin's start/stop state.
package gw.api.startableplugin
uses gw.api.startable.IStartablePlugin
uses gw.api.startable.StartablePluginCallbackHandler
uses gw.api.startable.StartablePluginState
uses gw.transaction.Transaction
uses java.lang.Thread
@Distributed
class HelloWorldDistributedStartablePlugin implements IStartablePlugin {
var _state : StartablePluginState
var _startedHowManyTimes = 0
var _callback : StartablePluginCallbackHandler
var _thread : Thread
override property get State() : StartablePluginState {
return _state
}
override function start(handler : StartablePluginCallbackHandler,
serverStartup : boolean) : void {
// Save the callback handler
_callback = handler
// Is server starting?
if (serverStartup) {
// Plugin is starting, too
_state = _callback.getState(Started)
// Log event
if (_state == Started) {
_callback.logStart("HelloWorldDistributedStartablePlugin:Started")
} else {
_callback.logStart("HelloWorldDistributedStartablePlugin:Stopped")
}
} else {
// Plugin start/stop state has changed
_state = Started
if (_callback.State != Started) {
changeState(Started) // Update saved start/stop state
}
_callback.logStart("HelloWorldDistributedStartablePlugin")
}
// If thread already exists, stop it before restarting it
if (_state == Started) and (_thread != null) {
_thread.stop()
}
// Increment local counter
_startedHowManyTimes++
// Create and start thread
var t = new Thread() {
function run() {
print("hello!")
}
t.Daemon=true
}
}
override function stop(serverStopping : boolean) : void {
// Stop thread
if (_thread != null) {
_thread.stop()
_thread = null
}
if (_callback != null) {
if (serverStopping) {
if (_state == Started) {
_callback.logStop("HelloWorldDistributedStartablePlugin:Started")
} else {
_callback.logStop("HelloWorldDistributedStartablePlugin:Stopped")
}
_callback = null
} else {
if (_callback.State != Stopped) {
changeState(Stopped) // Update saved start/stop state
}
_callback.logStop("HelloWorldDistributedStartablePlugin")
}
}
_state = Stopped
}
// Internal function to set the database state. If an exception occurs, retries 5 times.
private function changeState(newState : StartablePluginState) {
var tryCount = 0
while (_callback.State != newState && tryCount < 5) {
try {
Transaction.runWithNewBundle(\ bundle -> { _callback.setState(bundle, newState)},
User.util.UnrestrictedUser)
}
catch (e : java.lang.Exception) {
tryCount++
_callback.log(this.IntrinsicType.Name + " on attempt " + tryCount +
" caught " + (typeof e).Name + ": " + e.Message)
}
}
}
}
