Closed sabin26 closed 9 months ago
Resources are per worker, this is enforced by Dart's memory model (remember, it relies on isolation :-) ).
Your client will be initialized when the worker is started.
But there's currently no way to programmatically dispose of a service when its worker is stopped. I suppose you could use Dart's Finalizer
class to do the same, until Squadron provides a way to automatically dispose of resources allocated by the service class when the worker is stopped.
My 2 cents: in your implementation, late
has no effect because the client is initialized at construction time. late
is only a hint for a non-nullable field so that Dart does not complain that a field is not initialized. It will also allow for setting the field in your code even though it's marked final
. You should try to avoid resorting to late
in your code because it can lead to runtime errors if proper initialization is not carefully respected. Here, you're safe because _client
will be set before the class constructor body gets executed. You could also have written final BaseClient _client = Client();
, the effect will be the same.
Modified my actual implementation to remove the late
keyword as per your suggestion and will try using Finalizer
class tomorrow. Thank you ! I got the answer to my question. Closing the issue for now :)
I tried to use Finalizer
class but could not confirm that its callback is actually being triggered. I started and stopped worker multiple times and kept the application at idle for some decent time to test this.
@SquadronService(baseUrl: '/services')
class HttpService {
HttpService() : _client = Client() {
_finalizer.attach(this, _client, detach: this);
}
final Client _client;
static final _finalizer = Finalizer<Client>((final baseClient) {
print('I am closing the connection...'); // I do not see this on the console when debugging
baseClient.close();
});
@SquadronMethod()
Future<void> closeConnection() async {
_client.close();
_finalizer.detach(this); // I tested both conditions: with and without this line.
}
}
Could it be that it is used inside a worker and needs to be implemented differently? Maybe the variable _finalizer
is itself garbage collected?
Hello, that's the idea indeed but I won't be able to help you on this one... I've played with finalizers just a little bit and remember it is not straightforward. Also, I used them in the main isolate only, but yours are running in background threads that may be killed by Squadron -- that plus the fact that "No promises are made that the callback will ever be called" (https://api.flutter.dev/flutter/dart-core/Finalizer-class.html) so I really don't know if this behavior is normal or not!
Also, if you're testing on Web, keep in mind that "print()" functions called by the worker will print to the browser's console and the browser console only. It won't be visible in your debugger output.
I also read the docs on Finalizer and it does not guarentee that callback will be called. I looked into NativeFinalizer which guarentees it but its only for native resources via ffi. Maybe i will initialize and close the http client per request and not keep it hanging around.
I tested it on windows native platform (not web).
I've added ServiceInstaller
to support this use case.
If your service needs initialization just after the worker thread has started, implement ServiceInstaller.install()
.
If it needs to cleanup resources just before the worker thread is stopped, implement ServiceInstaller.uninstall()
.
squadron_builder does not support this yet, so don't jump on that feature right now, but it's coming. I'll keep you posted when squadron_builder is ready (hopefully next week).
If we are initializing resources separately with ServiceInstaller, shouldn't the variables be marked with late keyword?
I've just ran a test and squadron_builder needs no change to support the new ServiceInstaller
interface. So, it's working out of the box!
Regarding resource initialization, I would only implement the install()
method if setting up the service requires asynchronous operations, that cannot be awaited in the constructor. Otherwise, I'd use the constructor for initialization. In your case, I'd stick with the constructor and avoid the late
keyword.
What's most interesting is the uninstall()
method where you'll be able to dispose of resources acquired by the service.
Have fun!
Hello,
I realize I've been a bit harsh against the late
keyword. It's not only syntactic sugar (to tell Dart you'll be handling initialization in situations where the code analyzer cannot verify that you do), but it also has lazy semantics eg. to defer initialization until the result is effectively used.
I would still avoid having the declaration and the initialization at two different places. If you can initialize the field on the same line where it is declared, it makes it easy to assess that field initialization will never be forgotten.
class HttpService {
HttpService();
late final BaseClient _client = Client(); // _client will not be initialized before the first time it is used
}
I worked around to use a single http client per request. No more late initialization, no more cleanup to perform (when the request is completed, client is closed). There will be a new client for new request.
I was wondering if the workers in a pool share the same client, or each worker will have its own client in the following code. If they do not share the same client, then I will have to manually call the
closeConnection()
method by listening when the worker is removed in the workerpool in which case the callback should be called before the worker is actually removed.Also, will the client be initialized when a worker pool is started or will it defer until the worker is started?