Plugin not destructing

Hi Chris,

I have recently started to write on a Plugin for a custom Teensy-IO using MWorks 0.6.

This plugin establishes a connection to the Teensy within the initialization routine and then schedules 5 executions of a sync event in order to sync Teensy and MWorks time.

I do this (within Teensy::initialize()):

    scheduler = Scheduler::instance();
    shared_ptr<Teensy> this_one = component_shared_from_this<Teensy>();
    schedule_node = scheduler->scheduleMS(std::string(FILELINE ": ") + getTag(),
                                          1, //defer first start 1ms
                                          1000, //repeat every second
                                          5,//M_REPEAT_INDEFINITELY,
                                          boost::bind(send_sync_request_, this_one),
                                          M_DEFAULT_PRIORITY,
                                          M_DEFAULT_WARN_SLOP_US,
                                          M_DEFAULT_FAIL_SLOP_US,
                                          M_MISSED_EXECUTION_DROP);

As a result, I get 5 executions of the send_sync_request function. But doing this also prevents the plugin from ever reaching the destructor. I need to reset the serial port there (which works perfectly when no scheduling takes place). What am I doing wrong? Should I clear the shared_ptr manually and how would this look like exactly?

Thank you for your help,
Philipp

PS: I used the Eyelink Plugin as a template. I didn’t have time to check, but it might be it will also never destruct once started… this should be checked out.

Hi Philipp,

Because you use a shared_ptr in the call to boost::bind, schedule_node owns a reference to the Teensy instance. If schedule_node is a member of the Teensy class, then this creates a reference cycle, which prevents the instance from being destructed.

In order to break the cycle, you need to destroy schedule_node outside of the Teensy destructor. For an I/O device, a likely place to do this is in the stopDeviceIO method. Add the following:

if (schedule_node) {
    schedule_node->cancel();  // Ensure the task is stopped
    schedule_node.reset();    // Release the ScheduleTask instance
}

PS: I used the Eyelink Plugin as a template. I didn’t have time to check, but it might be it will also never destruct once started… this should be checked out.

OK, I’ll take a look.

Cheers,
Chris

Hi Chris,

thank you for your fast reply.

I can of course destroy the schedule_node within stopDeviceIO(), but the idea was that the clock syncing starts at plugin load time and ends when the plugin is unloaded. I can live with this not being possible, but what will happen when the server attempts to unload the plugin while it is running? Will stopDeviceIO() be called automatically then or will the reference cycle then still prevent the plugin from being unloaded?

For the case of the Eyelink plugin, I think I checked within the destructor whether the device was still running and if it was, I would stop and destroy the schedule nodes like you suggested. This was working just fine, but now, if I understand you right, the destructor will not even be called if a reference cycle exists, even if this cycle would break within the destructor. Is there perhaps another function that I can use? Something like “plugin_unload()”?

Best,
Philipp

Hi Philipp,

I can of course destroy the schedule_node within stopDeviceIO(), but the idea was that the clock syncing starts at plugin load time and ends when the plugin is unloaded.

OK, then you probably want to use a weak_ptr. In initialize, create schedule_node like this:

auto weak_this = boost::weak_ptr<Teensy>(component_shared_from_this<Teensy>());
auto functor = [weak_this]() {
    if (auto shared_this = weak_this.lock()) {
       send_sync_request_(shared_this);
    }
}
schedule_node = scheduler->scheduleMS(std::string(FILELINE ": ") + getTag(),
                                      1, //defer first start 1ms
                                      1000, //repeat every second
                                      M_REPEAT_INDEFINITELY,
                                      functor,
                                      M_DEFAULT_PRIORITY,
                                      M_DEFAULT_WARN_SLOP_US,
                                      M_DEFAULT_FAIL_SLOP_US,
                                      M_MISSED_EXECUTION_DROP);

This way, schedule_node does not own a reference to the Teensy instance, so it won’t prevent its destruction. You can then put the schedule_node shutdown code that I shared previously in the Teensy destructor.

For the case of the Eyelink plugin, I think I checked within the destructor whether the device was still running and if it was, I would stop and destroy the schedule nodes like you suggested. This was working just fine, but now, if I understand you right, the destructor will not even be called if a reference cycle exists, even if this cycle would break within the destructor.

Yes, you understand correctly. However, the EyeLink plugin works, because it does what I recommended in my previous message: It destroys the schedule node in stopDeviceIO. The similar code in the destructor could be removed, because if the schedule node still exists, the destructor will never run.

Chris

Thank you Chris, this works great !!

Now, is there any disadvantage to using the weak_ptr instead of shared_ptr? Can something go wrong when there are two scheduler nodes working in parallel, both with weak pointers?

Thanks again for the great help!
Philipp

Hi Philipp,

Now, is there any disadvantage to using the weak_ptr instead of shared_ptr?

The only disadvantage is the added complexity of needing to ensure that the pointed-to object still exists before (and while) you use it. My example code shows the right way to do this: First attempt to create a shared_ptr (i.e. strong reference) with the lock method, and if successful, hold on to the shared_ptr for as long as you need to use the object:

if (auto strong_ref = weak_ref.lock()) {
    // Do work with strong_ref
    // ...
    // strong_ref goes out of scope here, and strong reference is released
}

Can something go wrong when there are two scheduler nodes working in parallel, both with weak pointers?

Nothing can go wrong that can’t also go wrong using shared pointers. In both cases, if multiple threads are accessing the same object, then you need to use some synchronization mechanism (e.g. mutex locks) to prevent one thread from modifying shared data while another thread reads or writes that data.

Cheers,
Chris