Skip to content

Commit cb7d236

Browse files
Do not allow attach if store already open in isolate.
1 parent 2125e93 commit cb7d236

File tree

3 files changed

+78
-19
lines changed

3 files changed

+78
-19
lines changed

objectbox/lib/src/native/store.dart

+16-8
Original file line numberDiff line numberDiff line change
@@ -100,12 +100,7 @@ class Store {
100100
malloc.free(cStr);
101101
}
102102
}
103-
if (_openStoreDirectories.contains(_dbDir)) {
104-
throw UnsupportedError(
105-
'Cannot create multiple Store instances for the same directory. '
106-
'Please use a single Store or close() the previous instance before '
107-
'opening another one.');
108-
}
103+
_checkStoreDirectoryNotOpen();
109104
final model = Model(_defs.model);
110105

111106
final opt = C.opt();
@@ -225,9 +220,13 @@ class Store {
225220
// _weak = false so store can be closed.
226221
: _weak = false,
227222
_queriesCaseSensitiveDefault = queriesCaseSensitiveDefault,
228-
_dbDir = '' {
223+
_dbDir = path.context.canonicalize(_safeDatabasePath(databasePath)) {
229224
try {
230-
// Not checking if database directory is open as this allows it.
225+
// Do not allow attaching to a store that is already open in the current
226+
// isolate. While technically possible this is not the intended usage
227+
// and e.g. transactions would have to be carefully managed to not
228+
// overlap.
229+
_checkStoreDirectoryNotOpen();
231230

232231
final path = _safeDatabasePath(databasePath);
233232
final pathCStr = path.toNativeUtf8();
@@ -247,6 +246,15 @@ class Store {
247246
}
248247
}
249248

249+
void _checkStoreDirectoryNotOpen() {
250+
if (_openStoreDirectories.contains(_dbDir)) {
251+
throw UnsupportedError(
252+
'Cannot create multiple Store instances for the same directory in the same isolate. '
253+
'Please use a single Store, close() the previous instance before '
254+
'opening another one or attach to it in another isolate.');
255+
}
256+
}
257+
250258
void _checkStorePointer(Pointer cStore) {
251259
try {
252260
checkObxPtr(cStore, 'failed to create store');

objectbox/test/basics_test.dart

+62-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import 'dart:ffi' as ffi;
22
import 'dart:io';
3+
import 'dart:isolate';
34

5+
import 'package:async/async.dart';
46
import 'package:objectbox/internal.dart';
57
import 'package:objectbox/src/native/bindings/bindings.dart';
68
import 'package:objectbox/src/native/bindings/helpers.dart';
@@ -60,25 +62,47 @@ void main() {
6062
env.closeAndDelete();
6163
});
6264

63-
test('store attach', () {
65+
test('store attach fails if same isolate', () {
66+
final env = TestEnv('basics');
67+
expect(
68+
() => Store.attach(getObjectBoxModel(), env.dir.path),
69+
throwsA(predicate((UnsupportedError e) =>
70+
e.message!.contains('Cannot create multiple Store instances'))));
71+
env.closeAndDelete();
72+
});
73+
74+
test('store attach remains open if main store closed', () async {
6475
final env = TestEnv('basics');
6576
final store1 = env.store;
66-
final store2 = Store.attach(getObjectBoxModel(), env.dir.path);
67-
expect(store1, isNot(store2));
68-
expect(InternalStoreAccess.ptr(store1),
69-
isNot(InternalStoreAccess.ptr(store2)));
77+
final receivePort = ReceivePort();
78+
final received = StreamQueue<dynamic>(receivePort);
79+
await Isolate.spawn(storeAttachIsolate,
80+
StoreAttachIsolateInit(receivePort.sendPort, env.dir.path));
81+
final commandPort = await received.next as SendPort;
82+
83+
// Check native instance pointer is different.
84+
final store2Address = await received.next as int;
85+
expect(InternalStoreAccess.ptr(store1).address, isNot(store2Address));
7086

7187
final id = store1.box<TestEntity>().put(TestEntity(tString: 'foo'));
7288
expect(id, 1);
7389
// Close original store to test store remains open until all refs closed.
7490
store1.close();
7591
expect(true, Store.isOpen('testdata-basics'));
76-
final read = store2.box<TestEntity>().get(id);
77-
expect(read, isNotNull);
78-
expect(read!.tString, 'foo');
79-
store2.close();
92+
93+
// Read data with attached store.
94+
commandPort.send(id);
95+
final readtString = await received.next as String?;
96+
expect(readtString, isNotNull);
97+
expect(readtString, 'foo');
98+
99+
// Close attached store, should close store completely.
100+
commandPort.send(null);
101+
await received.next;
80102
expect(false, Store.isOpen('testdata-basics'));
81-
env.closeAndDelete();
103+
104+
// Dispose StreamQueue.
105+
await received.cancel();
82106
});
83107

84108
test('store is open', () {
@@ -166,3 +190,31 @@ void main() {
166190
Directory('basics').deleteSync(recursive: true);
167191
});
168192
}
193+
194+
class StoreAttachIsolateInit {
195+
SendPort sendPort;
196+
String path;
197+
198+
StoreAttachIsolateInit(this.sendPort, this.path);
199+
}
200+
201+
void storeAttachIsolate(StoreAttachIsolateInit init) async {
202+
final store2 = Store.attach(getObjectBoxModel(), init.path);
203+
204+
final commandPort = ReceivePort();
205+
init.sendPort.send(commandPort.sendPort);
206+
init.sendPort.send(InternalStoreAccess.ptr(store2).address);
207+
208+
await for (final message in commandPort) {
209+
if (message is int) {
210+
final read = store2.box<TestEntity>().get(message);
211+
init.sendPort.send(read?.tString);
212+
} else if (message == null) {
213+
store2.close();
214+
init.sendPort.send(null);
215+
break;
216+
}
217+
}
218+
219+
print('Store attach isolate finished');
220+
}

objectbox/test/isolates_test.dart

-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ Future<void> testUsingStoreFromIsolate(Store Function(dynamic) storeCreator,
9696
return responseCompleter.future;
9797
};
9898

99-
// FIXME Send path instead of reference when using attach.
10099
// Pass the store to the isolate
101100
final env = TestEnv('isolates');
102101
expect(await call(storeRefGetter(env)), equals('store set'));

0 commit comments

Comments
 (0)