Skip to content

Commit 6873731

Browse files
authored
Make it possible for CronetClient to own the lifetime of an existing CronetEngine (#1137)
1 parent ce0de37 commit 6873731

File tree

6 files changed

+65
-15
lines changed

6 files changed

+65
-15
lines changed

pkgs/cronet_http/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 1.1.1
2+
3+
* Make it possible to construct `CronetClient` with custom a `CronetEngine`
4+
while still allowing `CronetClient` to close the `CronetEngine`.
5+
16
## 1.1.0
27

38
* Use `package:http_image_provider` in the example application.

pkgs/cronet_http/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ void main() async {
4343
cacheMode: CacheMode.memory,
4444
cacheMaxSize: 2 * 1024 * 1024,
4545
userAgent: 'Book Agent');
46-
httpClient = CronetClient.fromCronetEngine(engine);
46+
httpClient = CronetClient.fromCronetEngine(engine, isOwned: true);
4747
} else {
4848
httpClient = IOClient(HttpClient()..userAgent = 'Book Agent');
4949
}
@@ -52,6 +52,7 @@ void main() async {
5252
'www.googleapis.com',
5353
'/books/v1/volumes',
5454
{'q': 'HTTP', 'maxResults': '40', 'printType': 'books'}));
55+
httpClient.close();
5556
}
5657
```
5758

pkgs/cronet_http/example/integration_test/cronet_configuration_test.dart

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import 'dart:io';
66

77
import 'package:cronet_http/cronet_http.dart';
8+
import 'package:http/http.dart';
89
import 'package:integration_test/integration_test.dart';
910
import 'package:test/test.dart';
1011

@@ -120,10 +121,39 @@ void testUserAgent() {
120121
});
121122
}
122123

124+
void testEngineClose() {
125+
group('engine close', () {
126+
test('multiple close', () {
127+
CronetEngine.build()
128+
..close()
129+
..close();
130+
});
131+
132+
test('request after close', () async {
133+
final closedEngine = CronetEngine.build()..close();
134+
final client = CronetClient.fromCronetEngine(closedEngine);
135+
await expectLater(() => client.get(Uri.https('example.com', '/')),
136+
throwsA(isA<ClientException>()));
137+
});
138+
139+
test('engine owned close', () {
140+
final engine = CronetEngine.build();
141+
CronetClient.fromCronetEngine(engine, closeEngine: true).close();
142+
});
143+
144+
test('engine not owned close', () {
145+
final engine = CronetEngine.build();
146+
CronetClient.fromCronetEngine(engine, closeEngine: false).close();
147+
engine.close();
148+
});
149+
});
150+
}
151+
123152
void main() {
124153
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
125154

126155
testCache();
127156
testInvalidConfigurations();
128157
testUserAgent();
158+
testEngineClose();
129159
}

pkgs/cronet_http/example/lib/main.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ void main() {
2121
cacheMode: CacheMode.memory,
2222
cacheMaxSize: 2 * 1024 * 1024,
2323
userAgent: 'Book Agent');
24-
httpClient = CronetClient.fromCronetEngine(engine);
24+
httpClient = CronetClient.fromCronetEngine(engine, closeEngine: true);
2525
} else {
2626
httpClient = IOClient(HttpClient()..userAgent = 'Book Agent');
2727
}

pkgs/cronet_http/lib/src/cronet_client.dart

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ enum CacheMode {
4949
/// An environment that can be used to make HTTP requests.
5050
class CronetEngine {
5151
late final jb.CronetEngine _engine;
52+
bool _isClosed = false;
5253

5354
CronetEngine._(this._engine);
5455

@@ -140,7 +141,12 @@ class CronetEngine {
140141
}
141142

142143
void close() {
143-
_engine.shutdown();
144+
if (!_isClosed) {
145+
_engine
146+
..shutdown()
147+
..release();
148+
}
149+
_isClosed = true;
144150
}
145151
}
146152

@@ -275,21 +281,24 @@ class CronetClient extends BaseClient {
275281
CronetEngine? _engine;
276282
bool _isClosed = false;
277283

278-
/// Indicates that [_engine] was constructed as an implementation detail for
279-
/// this [CronetClient] (i.e. was not provided as a constructor argument) and
280-
/// should be closed when this [CronetClient] is closed.
281-
final bool _ownedEngine;
284+
/// Indicates that [CronetClient] is responsible for closing [_engine].
285+
final bool _closeEngine;
282286

283-
CronetClient._(this._engine, this._ownedEngine) {
287+
CronetClient._(this._engine, this._closeEngine) {
284288
Jni.initDLApi();
285289
}
286290

287291
/// A [CronetClient] that will be initialized with a new [CronetEngine].
288292
factory CronetClient.defaultCronetEngine() => CronetClient._(null, true);
289293

290294
/// A [CronetClient] configured with a [CronetEngine].
291-
factory CronetClient.fromCronetEngine(CronetEngine engine) =>
292-
CronetClient._(engine, false);
295+
///
296+
/// If [closeEngine] is `true`, then [engine] will be closed when [close] is
297+
/// called on this [CronetClient]. This can simplify lifetime management if
298+
/// [engine] is only used in one [CronetClient].
299+
factory CronetClient.fromCronetEngine(CronetEngine engine,
300+
{bool closeEngine = false}) =>
301+
CronetClient._(engine, closeEngine);
293302

294303
/// A [CronetClient] configured with a [Future] containing a [CronetEngine].
295304
///
@@ -309,7 +318,7 @@ class CronetClient extends BaseClient {
309318
/// ```
310319
@override
311320
void close() {
312-
if (!_isClosed && _ownedEngine) {
321+
if (!_isClosed && _closeEngine) {
313322
_engine?.close();
314323
}
315324
_isClosed = true;
@@ -322,14 +331,19 @@ class CronetClient extends BaseClient {
322331
'HTTP request failed. Client is already closed.', request.url);
323332
}
324333

325-
_engine ??= CronetEngine.build();
334+
final engine = _engine ?? CronetEngine.build();
335+
_engine = engine;
336+
337+
if (engine._isClosed) {
338+
throw ClientException(
339+
'HTTP request failed. CronetEngine is already closed.', request.url);
340+
}
326341

327342
final stream = request.finalize();
328343
final body = await stream.toBytes();
329344
final responseCompleter = Completer<StreamedResponse>();
330-
final engine = _engine!._engine;
331345

332-
final builder = engine.newUrlRequestBuilder(
346+
final builder = engine._engine.newUrlRequestBuilder(
333347
request.url.toString().toJString(),
334348
jb.UrlRequestCallbackProxy.new1(
335349
_urlRequestCallbacks(request, responseCompleter)),

pkgs/cronet_http/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: cronet_http
2-
version: 1.1.0
2+
version: 1.1.1
33
description: >-
44
An Android Flutter plugin that provides access to the Cronet HTTP client.
55
repository: https://github.com/dart-lang/http/tree/master/pkgs/cronet_http

0 commit comments

Comments
 (0)