Class cast exception due to different class loaders

In our Kafka module, there is a audit profile type that allows for splitting of events, one way goes to local DB, the other goes to Kafka. When I install the module once, this works as expected without error.

Audit Log Splitter Profile Type

However, if I leave everything installed as-is and simply upgrade the module I start to have class cast exception errors and the reason is because there are different class loaders in play. As far as I can tell there is a class loader associated with the audit profile and if the main module changes that instance of the class loader is then different that what is used by the gateway hook, gateway script module, etc.

Fresh install - Audit Events have a consistent class loader

After Module Update - Multiple class loaders are present

Interestingly, I can see that the class loader instance which would come from AuditRecord is the same as before the module was updated.

So for a module which establishes an instance of something that will end up using a different class loader after the module gets updated later on, what is a good way to prevent class cast errors due to different class loaders?

Thanks,

Nick

This means your module has a ClassLoader leak, i.e. if your were to simply uninstall it there would still be references to classes from your module, and its ClassLoader, hanging around in memory.

Usually this means you didn’t unregistered all the things you registered and/or shutdown all the things you may have started (background threads or process, for example).

@Kevin.Herron yeah, if I completely removed the module, the instance of the audit profile would still be there.

If I can get the audit profile (by name or by type) would it be sufficient to use the shut down and start up methods on the AuditProfile?

I believe if I am removing the module altogether I should remove the audit profile but if I'm just updating the module the audit profile should remain in place.

Nick

"Updating" a module is shutting down and removing the old one and installing a new one. If the old one doesn't remove or shutdown everything then you leak.

It looks like AuditManager doesn't have a corresponding removeAuditProfileType, which is a failure on our part that we'll have to fix. There's probably not much you can do to work around this right now.

There's no update in place. You must remove everything during module shutdown, since you don't know if a replacement is coming. You also may have new code for the established instances that won't work if not replaced.

1 Like

@Kevin.Herron actually if there was an interface for audits, that might make this easier. It would be something similar to AlarmListener but for Audits. Something like "onAudit" and then we could just define where to send that data after that and it would be done in the gateway script module so start up and shut down would already be in place.

Really the only reason I am setting up an audit profile at all is just so I can capture the AuditRecord.

Something like this but for Audit records instead:

    private void setupAlarmManager(KafkaSettingsRecord config) {
        alarmListener = new AlarmListener() {
            @Override
            public void onActive(AlarmEvent alarmEvent) {
                scriptModule.sendEquipmentAlarm(alarmEvent, alarmEvent.getActiveData(), config);
            }

            @Override
            public void onClear(AlarmEvent alarmEvent) {
                scriptModule.sendEquipmentAlarm(alarmEvent, alarmEvent.getClearedData(), config);
            }

            @Override
            public void onAcknowledge(AlarmEvent alarmEvent) {
                scriptModule.sendEquipmentAlarm(alarmEvent, alarmEvent.getAckData(), config);
            }
        };

Nick

Consider building an "export" jar that will load once for the gateway's lifetime. Have it implement an audit profile that can be registered, but delegates to a separate class in your main jar that is supplied or replaced on gateway hook startup. Poor man's proper extension point. (:

1 Like

Right now we have alarms and tags working fine so we'll just be disabling sending of audit data to kafka.

If the module SDK is updated to have a method to remove an audit profile type or have an interface to grab the audit event without having to create a new audit type, we'll come back and update the gateway hook and/or gateway script module.

Added this to the features and ideas page here.

Nick

@Kevin.Herron thinking about this a bit more, if a method is added to allow an audit profile type to be removed, it will need to consider how to handle the instances of that type which are specified in the gateway and in the designer. Otherwise once the audit type is removed errors will happen when the gateway or designer goes to use a audit sink that is no longer there.

For our needs, an audit listener would be the simplest so we don't even have to handle an audit profile type.

Thanks,

Nick

I just stepped over your post as I'm started to build a Kafka Module for Ignition. Is your work on that something that you would be willing to share or did you build the module as an commercial product?

Marius, we do have a working producer kafka module that streams tag history, audit, and alarms. We have no intent to make it a commercial product and it is basically just for our use, which is to have centralized data instead of storage per site. Not sure I can share with you at this time, but more than happy to provide code snippets and detail how it works.

Rgds,

Nick

1 Like

We are testing out 8.1.25-SNAPSHOT and I wanted to verify that the string parameter taken by removeAuditProfileType is the typeId of the audit log type, like this:

Audit Log Type

Usage to Remove Audit Profile Type
image

Thanks,

Nick

@Kevin.Herron I had a chance to test the new function removeAuditProfileType today.

The setup hook contains this:

/*
try {
    context.getSchemaUpdater().updatePersistentRecords(AuditLogSFSettings.META);
    context.getAuditManager().addAuditProfileType(new AuditLogSFType(scriptModule));
} catch (Exception e) {
    log.error("Error setting up audit type", e.getCause());
}

And the shutdown hook contains this:

  try {
      this.context.getAuditManager().removeAuditProfileType("AuditLogSF");
  } catch (Exception e) {}

When the module is first installed, the audit type appears as expected:

However, whenever the shutdown hook is called (by making updates to the kafka settings persistent record but not uninstalling the module) the audit profile is indeed removed, but it never comes back until the module is reinstalled again.

So functionally, the issue is the audit profile type should be removed when the module is removed, but it is being removed and made inaccessible until the module is installed again. What I'm trying to get to happen is that when the persistent record is updated with the same module still installed, the audit profile type should remain.

Should note that I am still using 8.1.25-SNAPSHOT

Thanks,

Nick

Try moving the addAuditProfileType() to your hook's startup method. Setup is only supposed to run once.

setup, startup, and shutdown should all get called only once in a module’s lifecycle.

Why is updating your persistent record calling shutdown and removing the profile?

@Kevin.Herron you're right. There was actually a call to shut down in the record updated listener. Once that was removed, the audit profile stays put as expected until the module is removed.

Thanks for asking that question.

Nick

@BluegelMarius - there is an (out of date) public Kafka example our sales engineering department put together a few years ago publicly available: