Apps management in Jira Server just got improved

Reading Time: 3 minutes

Since Jira Server 8.2, we've fixed several issues that caused performance degradation in large Jira instances when installing or updating the Marketplace apps. Until now, every update of an app could make Jira slow down or even freeze for some time. This was one of the most impactful bugs on jira.atlasian.com causing friction to Jira users. You can read more about this fix in detail here or look at the charts below, that show how it looked before and after.

Jira 8.0 (before)

Jira 8.2 (after the changes)

Apart from the positive business impact (less friction while updating apps during the workday by Jira admins on large instances) with the changes made by us in the UPM plugin system app developers can optimize how your app is impacting Jira performance. How? Just please follow the instruction below.

New atlassian-plugin event: PluginTransactionEndEvent

ATLASSIAN-PLUGINS 5.2

Across Jira the following pattern was frequently used:

class SomeComponent {
    // some atlassian cache(s), could be remote
    ...
    @EventListener
    public void onPluginDisabled(PluginDisabledEvent pluginDisabledEvent) {              
        resetCaches();
    }    
    @EventListener
    public void onPluginEnabled(PluginEnabledEvent pluginEnabledEvent) {    
       resetCaches();
    }
    @EventListener
    public void onPluginModuleDisabled(PluginModuleDisabledEvent pluginModuleDisabledEvent) {    
        resetCaches();
    }
    @EventListener
    public void onPluginModuleEnabled(PluginModuleEnabledEvent pluginModuleEnabledEvent {    
        resetCaches();
    }
}  

A plugin operation like install/uninstall, enable/disable could trigger thousands of those events.

There is a new event in atlassian-plugins which wraps the existing events into a "plugin transaction", so a user operation like disabling a plugin (with all its modules) would be wrapped in a single "plugin transaction", disabling a single module will also wrapped in a single transaction.

New event in atlassian-plugins:

public class PluginTransactionEndEvent {
   public ImmutableList<Object> getEvents();
   public int numberOfEvents();
   public <T> boolean hasAnyEventOfTypeMatching(final Class<T> eventTypeClass, final Predicate<T> anyMatchEventPredicate);
   public boolean hasAnyEventWithModuleDescriptorMatching(final Predicate<ModuleDescriptor<?>> anyMatchModuleDescriptorPredicate);
}  

So when disabling a plugin with 100 modules you should receive:

  1. PluginTransactionStartEvent x 1
  2. PluginDisablingEvent x 1
  3. PluginModuleDisablingEvent + PluginModuleDisabledEvent x 100
  4. PluginDisabledEvent x 1
  5. PluginTransactionEndEvent x 1

So now you can base your logic related to plugin changes on the new event:

class SomeComponent {
    // some atlassian cache(s), could be remote
    ...
    @EventListener
    public void onPluginTransactionEndEvent(final PluginTransactionEndEvent event) {
        resetCaches();
    }
}

The logic in the event handler can be aware of the type of events which were part of the "plugin transaction":

@EventListener
public void onPluginTransactionEndEvent(final PluginTransactionEndEvent event) {
    // do smth if there was an event of type X matching ...
    if (event.hasAnyEventOfTypeMatching(
            PluginEnabledEvent.class,
            pluginEnabledEvent -> affectsI18n().test(pluginEnabledEvent.getPlugin()))) {
        clearCaches();
    }
    // do smth if there was an event with module matching ...
    if (event.hasAnyEventWithModuleDescriptorMatching(WebworkModuleDescriptor.class::isInstance)) {
        clearCaches();
    }
}

The new event is used from Jira Core 8.2.0. We are planning to apply this pattern in (bundled) plugins in 8.4.x. This event can be used in other products after upgrading atlassian-plugins (>=5.2.2). Soon we are planning to backport this change to the latest enterprise release 7.13.x.

There is a contract between Jira and plugins on thePluginEnabledEvent, where Jira internal state should reflect the just-enabled plugin. Unintentionally, with the change described above (in Jira 8.2.0) we broke this contract and fixed it in Jira 8.3.1. Despite the old contract being respected once again, we still recommend plugin developers to switch to the new PluginTransactionEndEvent.