Skip to content

Add hot restart hooks to support dart:ffi #75528

Open
@jonahwilliams

Description

@jonahwilliams

Consider a Dart usage of a C api through ffi. A common pattern is the usage of context objects that must be explicitly setup and torn down for a given thread. ( This is simplified from an example program I wrote using https://sol.gfxile.net/soloud/ )

final lib = ffi.DynamicLibrary.open('some.dll');

class SoundSystem {
  ffi.Pointer _audioContext = lib.createContext();

  SoundSystem() {
    lib.setupContext(_audioContext);
   }
   
   void playSound() {
     lib.playSound(_audioContext);
   }
   
   void dispose() {
     lib.destroyContext(_audioContext);
   }
}

While this works well enough when performing a hot reload, but a hot restart may cause a native crash as recreating the SoundSystem with cause another audio context to be created without destroying the previous one.

During normal program execution this isn't a problem, since the dispose can be connected to the correct part of the application teardown logic ( or the handled by the OS as the program exits).

Trying to do something like teardown the entire widget tree may solve this problem, but would be expensive as well as a large behavior change with unintended consequences. Instead, I would propose adding a binding hook that allows developers/library authors to run callbacks before the hot restart is performed. The risk should be lower since the feature is opt-in, and failures could be ignored since the isolate would be destroyed anyway (though detecting these failures without using something like a timeout will be tricky).

For example, the above code could be modified like:

final lib = ffi.DynamicLibrary.open('some.dll');

class SoundSystem {
  ffi.Pointer _audioContext = lib.createContext();

  SoundSystem() {
    if (kDebugMode) {
      SomeBinding.instance!.addHotRestartTeardown(dispose);
    }
    lib.setupContext(_audioContext);
   }
   
   void playSound() {
     lib.playSound(_audioContext);
   }
   
   void dispose() {
     lib.destroyContext(_audioContext);
   }
}

This won't work if the library is trying to be platform agnostic though - which means we might need to consider either dart level support or a convention around using a dart:developer API. For example, something like

import 'dart:developer';

final lib = ffi.DynamicLibrary.open('some.dll');

class SoundSystem {
  ffi.Pointer _audioContext = lib.createContext();

  SoundSystem() {
    if (kDebugMode) {
      registerExtension('dart.ext.hotRestartTeardown', dispose);
    }
    lib.setupContext(_audioContext);
   }
   
   void playSound() {
     lib.playSound(_audioContext);
   }
   
   void dispose() {
     lib.destroyContext(_audioContext);
   }
}

Unfortunately I don't think that will work out of the box, since the various different libraries would stomp on eachother with different callbacks

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Issues that are less important to the Flutter projectc: new featureNothing broken; request for a new capabilityteam-toolOwned by Flutter Tool teamtoolAffects the "flutter" command-line tool. See also t: labels.triaged-toolTriaged by Flutter Tool team

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions