perf: avoid duplicate bootstrap work on join
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import 'dart:io' show File;
|
import 'dart:io' show Directory, File;
|
||||||
|
|
||||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
|
import 'package:flutter/foundation.dart' show visibleForTesting;
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
@@ -9,7 +10,19 @@ import '../src/rust/api.dart' as rust;
|
|||||||
|
|
||||||
const String appSemverBaseline = 'v0.1.0';
|
const String appSemverBaseline = 'v0.1.0';
|
||||||
const String _sileroVadAsset = 'assets/models/silero_vad.onnx';
|
const String _sileroVadAsset = 'assets/models/silero_vad.onnx';
|
||||||
const String _tenVadAsset = 'assets/models/ten_vad.onnx';
|
|
||||||
|
typedef StorageDirectoryProvider = Future<Directory> Function();
|
||||||
|
typedef StorageInitializer = Future<void> Function(String dir);
|
||||||
|
|
||||||
|
Future<void>? _storageInitFuture;
|
||||||
|
Future<void>? _vadBootstrapFuture;
|
||||||
|
StorageDirectoryProvider _storageDirectoryProvider =
|
||||||
|
getApplicationSupportDirectory;
|
||||||
|
StorageInitializer _storageInitializer = _defaultStorageInitializer;
|
||||||
|
|
||||||
|
Future<void> _defaultStorageInitializer(String dir) {
|
||||||
|
return rust.initStorage(dir: dir);
|
||||||
|
}
|
||||||
|
|
||||||
Future<File> _copyBundledAssetToDocuments({
|
Future<File> _copyBundledAssetToDocuments({
|
||||||
required String assetPath,
|
required String assetPath,
|
||||||
@@ -29,16 +42,28 @@ Future<File> _copyBundledAssetToDocuments({
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> configureBundledVadModels() async {
|
Future<void> configureBundledVadModels() async {
|
||||||
|
final existing = _vadBootstrapFuture;
|
||||||
|
if (existing != null) {
|
||||||
|
await existing;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final bootstrap = _configureBundledVadModelsImpl();
|
||||||
|
_vadBootstrapFuture = bootstrap;
|
||||||
|
try {
|
||||||
|
await bootstrap;
|
||||||
|
} catch (_) {
|
||||||
|
_vadBootstrapFuture = null;
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _configureBundledVadModelsImpl() async {
|
||||||
final silero = await _copyBundledAssetToDocuments(
|
final silero = await _copyBundledAssetToDocuments(
|
||||||
assetPath: _sileroVadAsset,
|
assetPath: _sileroVadAsset,
|
||||||
fileName: 'silero_vad.onnx',
|
fileName: 'silero_vad.onnx',
|
||||||
);
|
);
|
||||||
final ten = await _copyBundledAssetToDocuments(
|
|
||||||
assetPath: _tenVadAsset,
|
|
||||||
fileName: 'ten_vad.onnx',
|
|
||||||
);
|
|
||||||
await rust.setVadModelPath(path: silero.path);
|
await rust.setVadModelPath(path: silero.path);
|
||||||
await rust.setTenVadModelPath(path: ten.path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve the human-readable app version displayed in About.
|
/// Resolve the human-readable app version displayed in About.
|
||||||
@@ -69,15 +94,45 @@ String appVersionFromBuildNumber({
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> wireStorage() async {
|
Future<void> wireStorage() async {
|
||||||
|
final existing = _storageInitFuture;
|
||||||
|
if (existing != null) {
|
||||||
|
await existing;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final initFuture = _wireStorageImpl();
|
||||||
|
_storageInitFuture = initFuture;
|
||||||
|
await initFuture;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _wireStorageImpl() async {
|
||||||
|
var initialized = false;
|
||||||
try {
|
try {
|
||||||
final dir = await getApplicationSupportDirectory();
|
final dir = await _storageDirectoryProvider();
|
||||||
await rust.initStorage(dir: dir.path);
|
await _storageInitializer(dir.path);
|
||||||
|
initialized = true;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// Best-effort; missing storage just means no identity persistence
|
// Best-effort; missing storage just means no identity persistence
|
||||||
// and no bookmark list this session.
|
// and no bookmark list this session.
|
||||||
|
} finally {
|
||||||
|
if (!initialized) {
|
||||||
|
_storageInitFuture = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
void debugResetStorageBootstrap({
|
||||||
|
StorageDirectoryProvider? storageDirectoryProvider,
|
||||||
|
StorageInitializer? storageInitializer,
|
||||||
|
}) {
|
||||||
|
_storageInitFuture = null;
|
||||||
|
_vadBootstrapFuture = null;
|
||||||
|
_storageDirectoryProvider =
|
||||||
|
storageDirectoryProvider ?? getApplicationSupportDirectory;
|
||||||
|
_storageInitializer = storageInitializer ?? _defaultStorageInitializer;
|
||||||
|
}
|
||||||
|
|
||||||
rust.BridgeNetworkState _mapConnectivity(List<ConnectivityResult> results) {
|
rust.BridgeNetworkState _mapConnectivity(List<ConnectivityResult> results) {
|
||||||
if (results.isEmpty) return rust.BridgeNetworkState.unknown;
|
if (results.isEmpty) return rust.BridgeNetworkState.unknown;
|
||||||
final allNone = results.every((r) => r == ConnectivityResult.none);
|
final allNone = results.every((r) => r == ConnectivityResult.none);
|
||||||
|
|||||||
@@ -1,8 +1,19 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
import 'package:chanora_flutter/services/app_bootstrap.dart';
|
import 'package:chanora_flutter/services/app_bootstrap.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
setUp(() {
|
||||||
|
debugResetStorageBootstrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() {
|
||||||
|
debugResetStorageBootstrap();
|
||||||
|
});
|
||||||
|
|
||||||
test('app version appends platform build number', () {
|
test('app version appends platform build number', () {
|
||||||
expect(
|
expect(
|
||||||
appVersionFromBuildNumber(
|
appVersionFromBuildNumber(
|
||||||
@@ -19,4 +30,58 @@ void main() {
|
|||||||
'v1.2.3-rc.4',
|
'v1.2.3-rc.4',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('wireStorage reuses the in-flight initialization future', () async {
|
||||||
|
final completer = Completer<void>();
|
||||||
|
final tempDir = await Directory.systemTemp.createTemp(
|
||||||
|
'chanora-app-bootstrap-test',
|
||||||
|
);
|
||||||
|
addTearDown(() => tempDir.delete(recursive: true));
|
||||||
|
|
||||||
|
var initCalls = 0;
|
||||||
|
debugResetStorageBootstrap(
|
||||||
|
storageDirectoryProvider: () async => tempDir,
|
||||||
|
storageInitializer: (dir) async {
|
||||||
|
initCalls += 1;
|
||||||
|
await completer.future;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
final first = wireStorage();
|
||||||
|
final second = wireStorage();
|
||||||
|
var secondCompleted = false;
|
||||||
|
second.then((_) => secondCompleted = true);
|
||||||
|
|
||||||
|
await Future<void>.delayed(Duration.zero);
|
||||||
|
expect(initCalls, 1);
|
||||||
|
expect(secondCompleted, isFalse);
|
||||||
|
|
||||||
|
completer.complete();
|
||||||
|
await Future.wait([first, second]);
|
||||||
|
expect(initCalls, 1);
|
||||||
|
expect(secondCompleted, isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('wireStorage retries after a failed initialization attempt', () async {
|
||||||
|
final tempDir = await Directory.systemTemp.createTemp(
|
||||||
|
'chanora-app-bootstrap-test',
|
||||||
|
);
|
||||||
|
addTearDown(() => tempDir.delete(recursive: true));
|
||||||
|
|
||||||
|
var initCalls = 0;
|
||||||
|
debugResetStorageBootstrap(
|
||||||
|
storageDirectoryProvider: () async => tempDir,
|
||||||
|
storageInitializer: (dir) async {
|
||||||
|
initCalls += 1;
|
||||||
|
if (initCalls == 1) {
|
||||||
|
throw const FileSystemException('boom');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
await wireStorage();
|
||||||
|
await wireStorage();
|
||||||
|
|
||||||
|
expect(initCalls, 2);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user