Create stack trace across remote isolates

This commit is contained in:
Simon Binder 2024-02-03 17:07:20 +01:00
parent 7f7d2ab1b0
commit 0877e761e4
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
5 changed files with 72 additions and 7 deletions

View File

@ -9,6 +9,8 @@
`database.update` outside of a transaction and then calling
`UpdateStatement.write` inside of a transaction will now perform the update
inside of the transaction, instead of causing a deadlock.
- Improve stack traces for errors happening on drift isolates (which includes
usages of `NativeDatabase.createInBackground`).
## 2.15.0

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:drift/src/runtime/api/runtime_api.dart';
import 'package:meta/meta.dart';
import 'package:stack_trace/stack_trace.dart';
import 'package:stream_channel/stream_channel.dart';
import '../runtime/cancellation_zone.dart';
@ -90,8 +91,7 @@ class DriftCommunication {
final request = _pendingRequests.remove(requestId);
request?.completeWithError(
DriftRemoteException._(msg.error, msg.stackTrace));
_pendingRequests.remove(msg.requestId);
DriftRemoteException._(msg.error, msg.stackTrace), msg.stackTrace);
} else if (msg is Request) {
_incomingRequests.add(msg);
} else if (msg is CancelledResponse) {
@ -184,8 +184,15 @@ class _PendingRequest {
_PendingRequest(this.completer, this.requestTrace);
void completeWithError(Object error) {
completer.completeError(error, requestTrace);
void completeWithError(Object error, [StackTrace? trace]) {
completer.completeError(
error,
trace == null
? requestTrace
: Chain([
if (trace is Chain) ...trace.traces else Trace.from(trace),
Trace.from(requestTrace)
]));
}
}
@ -194,6 +201,11 @@ class _PendingRequest {
class ConnectionClosedException implements Exception {
/// Constant constructor.
const ConnectionClosedException();
@override
String toString() {
return 'Channel was closed before receiving a response';
}
}
/// An exception reported on the other end of a drift remote protocol.

View File

@ -9,7 +9,7 @@ import '../runtime/cancellation_zone.dart';
import 'communication.dart';
import 'protocol.dart';
/// The implementation of a drift server, manging remote channels to send
/// The implementation of a drift server, managing remote channels to send
/// database requests.
@internal
class ServerImplementation implements DriftServer {

View File

@ -17,6 +17,7 @@ dependencies:
stream_channel: ^2.1.0
sqlite3: ^2.2.0
path: ^1.8.0
stack_trace: ^1.11.1
dev_dependencies:
archive: ^3.3.1
@ -35,6 +36,5 @@ dev_dependencies:
mockito: ^5.4.3
rxdart: ^0.27.0
shelf: ^1.3.0
stack_trace: ^1.10.0
test_descriptor: ^2.0.1
vm_service: ^13.0.0

View File

@ -9,6 +9,7 @@ import 'package:drift/native.dart';
import 'package:drift/src/isolate.dart';
import 'package:drift/src/remote/communication.dart';
import 'package:mockito/mockito.dart';
import 'package:stack_trace/stack_trace.dart';
import 'package:test/test.dart';
import 'generated/todos.dart';
@ -110,7 +111,7 @@ void main() {
// The stack trace of remote exceptions should point towards the actual
// source making the faulty call.
expect(s.toString(), contains('main.<anonymous closure>'));
expect(s.toString(), contains('test/isolate_test.dart'));
}
// Check that isolate is still usable
@ -467,6 +468,56 @@ void _runTests(FutureOr<DriftIsolate> Function() spawner, bool terminateIsolate,
);
}
});
if (!serialize) {
test('provides complete stack traces for exceptions', () async {
// This functions have a name so that we can assert they show up in stack
// traces.
Future<void> faultyMigration() async {
await database.customStatement('invalid syntax');
}
database.migration = MigrationStrategy(onCreate: (m) async {
await faultyMigration();
});
try {
// The database is opened at the first statement, which will also run the
// faulty migration logic.
Future<void> useDatabase() async {
await database.customSelect('SELECT 1').get();
}
await useDatabase();
fail('Should have failed in the migration');
} on DriftRemoteException catch (e, s) {
final trace = Chain.forTrace(s);
// Innermost trace: The query failing on the remote isolate
expect(trace.traces, hasLength(4));
expect(
trace.traces[0].frames[0].toString(), contains('package:sqlite3/'));
// Then the next one: The migration being called in this isolate
expect(
trace.traces[1].frames,
contains(isA<Frame>().having(
(e) => e.member, 'member', contains('faultyMigration'))));
// This in turn is called by the server when trying to open the database.
expect(
trace.traces[2].frames,
contains(isA<Frame>().having((e) => e.member, 'member',
contains('_ServerDbUser.beforeOpen'))));
// Which, finally, happened because we were opening the database here.
expect(
trace.traces[3].frames,
contains(isA<Frame>()
.having((e) => e.member, 'member', contains('useDatabase'))));
}
});
}
}
DatabaseConnection _backgroundConnection() {