Skip to content

Commit

Permalink
fix: Handle private class in isolate (#152)
Browse files Browse the repository at this point in the history
* fix: Handle private class in isolate
* use pubspec_overrides file
  • Loading branch information
j4qfrost committed Nov 23, 2022
1 parent ba1c80b commit 28b8745
Show file tree
Hide file tree
Showing 22 changed files with 94 additions and 70 deletions.
4 changes: 0 additions & 4 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ jobs:
git config --local user.name "github-actions[bot]"
- name: Install melos
run: dart pub global activate melos
- name: Strip overrides
run: melos strip-overrides
- name: Format
run: dart fix --apply && dart format --fix .
- name: Uptick versions
Expand All @@ -43,8 +41,6 @@ jobs:
run: melos cache-source
- name: Publish package
run: melos publish --no-dry-run --git-tag-version --yes
- name: Apply overrides
run: melos apply-overrides
- name: Push tags
uses: CasperWA/push-protected@v2
with:
Expand Down
8 changes: 2 additions & 6 deletions melos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ ignore:
command:
version:
linkToCommits: true
bootstrap:
usePubspecOverrides: true
scripts:
test-unit:
run: melos exec --ignore "*common*" --ignore "*application*" --ignore "*dependency*" -- "dart test -j1 -x cli"
Expand All @@ -36,12 +38,6 @@ scripts:
run: melos exec -- "mkdir %PUB_CACHE%\hosted\pub.dev\$MELOS_PACKAGE_NAME-$MELOS_PACKAGE_VERSION && xcopy $MELOS_PACKAGE_PATH %PUB_CACHE%\hosted\pub.dev\$MELOS_PACKAGE_NAME-$MELOS_PACKAGE_VERSION && yq -i del(.dependency_overrides) %PUB_CACHE%\hosted\pub.dev\$MELOS_PACKAGE_NAME-$MELOS_PACKAGE_VERSION\pubspec.yaml"
select-pacakge:
no-private: true
strip-overrides:
run: melos exec -- "yq -i 'del(.dependency_overrides)' '\$MELOS_PACKAGE_PATH/pubspec.yaml'"
select-pacakge:
no-private: true
apply-overrides:
run: melos exec -- "if [ -f '\$MELOS_PACKAGE_PATH/overrides.yaml' ]; then yq -i '. *= load(\"\$MELOS_PACKAGE_PATH/overrides.yaml\")' '\$MELOS_PACKAGE_PATH/pubspec.yaml'; fi"
hard-clean:
run: melos exec -- "rm -rf '\$MELOS_PACKAGE_PATH/.dart_tool' '\$MELOS_PACKAGE_PATH/pubspec.lock'"
environment:
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion packages/common_test/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ environment:
sdk: ">=2.17.0 <3.0.0"
dependencies:
conduit: ^3.2.11
conduit_common: ^3.2.10
conduit_common: ^3.2.10
File renamed without changes.
50 changes: 30 additions & 20 deletions packages/conduit/lib/src/application/application.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,20 @@ export 'service_registry.dart';
/// An application object opens HTTP listeners that forward requests to instances of your [ApplicationChannel].
/// It is unlikely that you need to use this class directly - the `conduit serve` command creates an application object
/// on your behalf.
class Application<T extends ApplicationChannel?> {
class Application<T extends ApplicationChannel> {
/// A list of isolates that this application supervises.
List<ApplicationIsolateSupervisor> supervisors = [];

/// The [ApplicationServer] listening for HTTP requests while under test.
///
/// This property is only valid when an application is started via [startOnCurrentIsolate].
ApplicationServer? server;
late ApplicationServer server;

/// The [ApplicationChannel] handling requests while under test.
///
/// This property is only valid when an application is started via [startOnCurrentIsolate]. You use
/// this value to access elements of your application channel during testing.
T? get channel => server?.channel as T?;
T get channel => server.channel as T;

/// The logger that this application will write messages to.
///
Expand All @@ -60,7 +60,7 @@ class Application<T extends ApplicationChannel?> {
/// This value will return to false after [stop] has completed.
bool get isRunning => _hasFinishedLaunching;
bool _hasFinishedLaunching = false;
ChannelRuntime? get _runtime => RuntimeContext.current[T] as ChannelRuntime?;
ChannelRuntime get _runtime => RuntimeContext.current[T] as ChannelRuntime;

/// Starts this application, allowing it to handle HTTP requests.
///
Expand All @@ -75,7 +75,7 @@ class Application<T extends ApplicationChannel?> {
///
/// See also [startOnCurrentIsolate] for starting an application when running automated tests.
Future start({int numberOfInstances = 1, bool consoleLogging = false}) async {
if (server != null || supervisors.isNotEmpty) {
if (supervisors.isNotEmpty) {
throw StateError(
"Application error. Cannot invoke 'start' on already running Conduit application.",
);
Expand All @@ -90,7 +90,7 @@ class Application<T extends ApplicationChannel?> {
}

try {
await _runtime!.runGlobalInitialization(options);
await _runtime.runGlobalInitialization(options);

for (var i = 0; i < numberOfInstances; i++) {
final supervisor = await _spawn(
Expand Down Expand Up @@ -120,7 +120,7 @@ class Application<T extends ApplicationChannel?> {
/// An application started in this way will run on the same isolate this method is invoked on.
/// Performance is limited when running the application with this method; prefer to use [start].
Future startOnCurrentIsolate() async {
if (server != null || supervisors.isNotEmpty) {
if (supervisors.isNotEmpty) {
throw StateError(
"Application error. Cannot invoke 'test' on already running Conduit application.",
);
Expand All @@ -129,11 +129,11 @@ class Application<T extends ApplicationChannel?> {
options.address ??= InternetAddress.loopbackIPv4;

try {
await _runtime!.runGlobalInitialization(options);
await _runtime.runGlobalInitialization(options);

server = ApplicationServer(_runtime!.channelType, options, 1);
server = ApplicationServer(_runtime.channelType, options, 1);

await server!.start();
await server.start();
_hasFinishedLaunching = true;
} catch (e, st) {
logger.severe("$e", this, st);
Expand All @@ -148,14 +148,24 @@ class Application<T extends ApplicationChannel?> {
/// The [ServiceRegistry] will close any of its resources.
Future stop() async {
_hasFinishedLaunching = false;
await Future.wait(supervisors.map((s) => s.stop()));
if (server != null && server!.server != null) {
await server!.server!.close(force: true);
await Future.wait(supervisors.map((s) => s.stop()))
.onError((error, stackTrace) {
if (error.runtimeType.toString() == 'LateError') {
throw StateError(
'Channel type $T was not loaded in the current isolate. Check that the class was declared and public.',
);
}
throw error! as Error;
});

try {
await server.server!.close(force: true);
} catch (e) {
logger.severe(e);
}

await ServiceRegistry.defaultInstance.close();
_hasFinishedLaunching = false;
server = null;
supervisors = [];

logger.clearListeners();
Expand All @@ -175,11 +185,11 @@ class Application<T extends ApplicationChannel?> {

final server = ApplicationServer(runtime.channelType, config, 1);

await server.channel!.prepare();
await server.channel.prepare();

final doc = await server.channel!.documentAPI(projectSpec);
final doc = await server.channel.documentAPI(projectSpec);

await server.channel!.close();
await server.channel.close();

return doc;
}
Expand All @@ -194,9 +204,9 @@ class Application<T extends ApplicationChannel?> {
}) async {
final receivePort = ReceivePort();

final libraryUri = _runtime!.libraryUri;
final typeName = _runtime!.name;
final entryPoint = _runtime!.isolateEntryPoint;
final libraryUri = _runtime.libraryUri;
final typeName = _runtime.name;
final entryPoint = _runtime.isolateEntryPoint;

final initialMessage = ApplicationInitialServerMessage(
typeName,
Expand Down
14 changes: 7 additions & 7 deletions packages/conduit/lib/src/application/application_server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class ApplicationServer {
/// You should not need to invoke this method directly.
ApplicationServer(this.channelType, this.options, this.identifier) {
channel = (RuntimeContext.current[channelType] as ChannelRuntime)
.instantiateChannel()!
.instantiateChannel()
..server = this
..options = options;
}
Expand All @@ -31,7 +31,7 @@ class ApplicationServer {
HttpServer? server;

/// The instance of [ApplicationChannel] serving requests.
ApplicationChannel? channel;
late ApplicationChannel channel;

/// The cached entrypoint of [channel].
late Controller entryPoint;
Expand Down Expand Up @@ -62,13 +62,13 @@ class ApplicationServer {
Future start({bool shareHttpServer = false}) async {
logger.fine("ApplicationServer($identifier).start entry");

await channel!.prepare();
await channel.prepare();

entryPoint = channel!.entryPoint;
entryPoint = channel.entryPoint;
entryPoint.didAddToChannel();

logger.fine("ApplicationServer($identifier).start binding HTTP");
final securityContext = channel!.securityContext;
final securityContext = channel.securityContext;
if (securityContext != null) {
_requiresHTTPS = true;

Expand Down Expand Up @@ -102,7 +102,7 @@ class ApplicationServer {
await server!.close(force: true);
}
logger.fine("ApplicationServer($identifier).close Closing channel");
await channel?.close();
await channel.close();

// This is actually closed by channel.messageHub.close, but this shuts up the analyzer.
hubSink?.close();
Expand All @@ -118,7 +118,7 @@ class ApplicationServer {
logger.fine("ApplicationServer($identifier).didOpen start listening");
server!.map((baseReq) => Request(baseReq)).listen(entryPoint.receive);

channel!.willStartReceivingRequests();
channel.willStartReceivingRequests();
logger.info("Server conduit/$identifier started.");
}

Expand Down
2 changes: 1 addition & 1 deletion packages/conduit/lib/src/application/channel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ abstract class ChannelRuntime {
Uri get libraryUri;
IsolateEntryFunction get isolateEntryPoint;

ApplicationChannel? instantiateChannel();
ApplicationChannel instantiateChannel();

Future? runGlobalInitialization(ApplicationOptions config);
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ class ApplicationIsolateSupervisor {
logger.severe(
"Isolate ($identifier) not responding to stop message, terminating.",
);
} finally {
isolate.kill();
}

Expand Down
1 change: 1 addition & 0 deletions packages/conduit/lib/src/cli/commands/serve.dart
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ class CLIServer extends CLICommand with CLIProject {
);

errorPort!.listen((msg) {
print(msg);
if (msg is List) {
startupCompleter.completeError(
msg.first as Object,
Expand Down
24 changes: 12 additions & 12 deletions packages/conduit/lib/src/runtime/impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,35 +17,35 @@ import 'package:conduit_runtime/runtime.dart';
class ChannelRuntimeImpl extends ChannelRuntime implements SourceCompiler {
ChannelRuntimeImpl(this.type);

final ClassMirror? type;
final ClassMirror type;

static const _globalStartSymbol = #initializeApplication;

@override
String get name => MirrorSystem.getName(type!.simpleName);
String get name => MirrorSystem.getName(type.simpleName);

@override
IsolateEntryFunction get isolateEntryPoint => isolateServerEntryPoint;

@override
Uri get libraryUri => (type!.owner! as LibraryMirror).uri;
Uri get libraryUri => (type.owner! as LibraryMirror).uri;

bool get hasGlobalInitializationMethod {
return type!.staticMembers[_globalStartSymbol] != null;
return type.staticMembers[_globalStartSymbol] != null;
}

@override
Type get channelType => type!.reflectedType;
Type get channelType => type.reflectedType;

@override
ApplicationChannel? instantiateChannel() {
return type!.newInstance(Symbol.empty, []).reflectee as ApplicationChannel?;
ApplicationChannel instantiateChannel() {
return type.newInstance(Symbol.empty, []).reflectee as ApplicationChannel;
}

@override
Future? runGlobalInitialization(ApplicationOptions config) {
if (hasGlobalInitializationMethod) {
return type!.invoke(_globalStartSymbol, [config]).reflectee as Future?;
return type.invoke(_globalStartSymbol, [config]).reflectee as Future?;
}

return null;
Expand All @@ -56,7 +56,7 @@ class ChannelRuntimeImpl extends ChannelRuntime implements SourceCompiler {
ApplicationChannel channel,
) {
final documenter = reflectType(APIComponentDocumenter);
return type!.declarations.values
return type.declarations.values
.whereType<VariableMirror>()
.where(
(member) =>
Expand All @@ -70,8 +70,8 @@ class ChannelRuntimeImpl extends ChannelRuntime implements SourceCompiler {

@override
Future<String> compile(BuildContext ctx) async {
final className = MirrorSystem.getName(type!.simpleName);
final originalFileUri = type!.location!.sourceUri.toString();
final className = MirrorSystem.getName(type.simpleName);
final originalFileUri = type.location!.sourceUri.toString();
final globalInitBody = hasGlobalInitializationMethod
? "await $className.initializeApplication(config);"
: "";
Expand Down Expand Up @@ -133,7 +133,7 @@ void isolateServerEntryPoint(ApplicationInitialServerMessage params) {
final channelSourceLibrary =
currentMirrorSystem().libraries[params.streamLibraryURI]!;
final channelType = channelSourceLibrary
.declarations[Symbol(params.streamTypeName)] as ClassMirror?;
.declarations[Symbol(params.streamTypeName)]! as ClassMirror;

final runtime = ChannelRuntimeImpl(channelType);

Expand Down
File renamed without changes.
8 changes: 4 additions & 4 deletions packages/conduit/test/auth/auth_redirect_controller_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ void main() {
});

setUp(() async {
(application.channel!.authServer!.delegate as InMemoryAuthStorage).reset();
(application.channel!.authServer!.delegate as InMemoryAuthStorage)
(application.channel.authServer!.delegate as InMemoryAuthStorage).reset();
(application.channel.authServer!.delegate as InMemoryAuthStorage)
.createUsers(2);
});

Expand Down Expand Up @@ -238,7 +238,7 @@ void main() {

final redirectURI = Uri.parse(resp.headers["location"]!.first);
final codeParam = redirectURI.queryParameters["code"];
final token = await application.channel!.authServer!
final token = await application.channel.authServer!
.exchange(codeParam, "com.stablekernel.scoped", "kilimanjaro");
expect(token.scopes!.length, 1);
expect(token.scopes!.first.isExactly("user"), true);
Expand All @@ -261,7 +261,7 @@ void main() {

final redirectURI = Uri.parse(resp.headers["location"]!.first);
final codeParam = redirectURI.queryParameters["code"];
final token = await application.channel!.authServer!
final token = await application.channel.authServer!
.exchange(codeParam, "com.stablekernel.scoped", "kilimanjaro");
expect(token.scopes!.length, 2);
expect(token.scopes!.any((s) => s.isExactly("user")), true);
Expand Down
20 changes: 20 additions & 0 deletions packages/conduit/test/db/entity_mirrors_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,18 @@ void main() {
fail("unreachable");
} on UnsupportedError {}
});
test("Private channel fails and notifies with appropriate message", () async {
final crashingApp = Application<_PrivateChannel>();
try {
await crashingApp.start();
expect(true, false);
} catch (e) {
expect(
e.toString(),
"Bad state: Channel type _PrivateChannel was not loaded in the current isolate. Check that the class was declared and public.",
);
}
});
}

class TestModel extends ManagedObject<_TestModel> implements _TestModel {}
Expand Down Expand Up @@ -76,3 +88,11 @@ class TypeRepo {
TypeMirror typeOf(Symbol symbol) {
return (reflectClass(TypeRepo).declarations[symbol] as VariableMirror).type;
}

class _PrivateChannel extends ApplicationChannel {
@override
Controller get entryPoint {
final router = Router();
return router;
}
}
Loading

0 comments on commit 28b8745

Please sign in to comment.