tekartik / sembast.dart

Simple io database
BSD 2-Clause "Simplified" License
780 stars 64 forks source link

(OS Error: Read-only file system, errno = 30) #202

Closed phillwiggins closed 4 years ago

phillwiggins commented 4 years ago

I keep seeing this error when trying to write to my DB. I have seen in the past in an old issue but it's reappeared. I'm currently trying to write to a DB but hit the issue of (OS Error: Read-only file system, errno = 30). Any idea why?

My Sembast DB:-

class GenericDB<T extends Storable> {
  GenericDB(this._dbKey, this._converter, String _dbTableKey) {
    _dbFactory = databaseFactoryIo;
    _store = stringMapStoreFactory.store(_dbTableKey);
  }

  final GenericStorableConverter<T> _converter;
  final String _dbKey;

  DatabaseFactory _dbFactory;
  StoreRef<String, Map<String, dynamic>> _store;

  Future<Database> get _db async {
    final appDocDir = await getApplicationDocumentsDirectory();
    await appDocDir.create(recursive: true);
    final path = join(appDocDir.path, _dbKey);
    final db = await _dbFactory.openDatabase(path, version: 1);
    print('TESTDB: ${db.toString()}');
    return db;
  }

  Future<ApiResponse> add(Storable item) async {
    final values = _converter.toMap(item);
    await _store.record(item.objectId).put(await _db, values);
    return await getById(item.objectId);
  }

  Future<ApiResponse> addAll<Storable>(List<T> items) async {
    final itemsInDb = <T>[];

    for (var item in items) {
      final response = await add(item);
      if (response.success) {
        final T itemInDB = response.result;
        itemsInDb.add(itemInDB);
      }
    }

    if (itemsInDb.isEmpty) {
      return errorResponse;
    } else {
      return ApiResponse(true, 200, itemsInDb, null);
    }
  }

Flutter Doctor (Im using AS)

Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel beta, 1.19.0-4.3.pre, on Microsoft Windows [Version 10.0.19041.388], locale en-GB)

[√] Android toolchain - develop for Android devices (Android SDK version 29.0.3)
[√] Android Studio (version 4.0)
[!] IntelliJ IDEA Community Edition (version 2018.1)
    X Flutter plugin not installed; this adds Flutter specific functionality.
    X Dart plugin not installed; this adds Dart specific functionality.
[√] Connected device (1 available)

! Doctor found issues in 1 category.

My error output (You can see the print statement outputting something that looks like it has worked just before returning the created DB in my code snippet)

TESTDB: {path: /data/user/0/com.purewowstudio.bodycal_new/app_flutter/bc_repository.db, version: 1, stores: [{name: _main, count: 0}, {name: repository_user, count: 1}, {name: repository_diet_plan, count: 8}, {name: repository_goal, count: 0}, {name: repository_goal_macronutrients, count: 0}], exportStat: {lineCount: 11, obsoleteLineCount: 1, compactCount: 0}}
E/flutter ( 9926): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: FileSystemException: Cannot create file, path = '/bc_repository.db' (OS Error: Read-only file system, errno = 30)
E/flutter ( 9926): #0      _wrap.<anonymous closure> (package:sembast/src/io/file_system_io.dart:103:7)
E/flutter ( 9926): #1      _rootRunUnary (dart:async/zone.dart:1198:47)
E/flutter ( 9926): #2      _CustomZone.runUnary (dart:async/zone.dart:1100:19)
E/flutter ( 9926): #3      _FutureListener.handleError (dart:async/future_impl.dart:160:20)
E/flutter ( 9926): #4      Future._propagateToListeners.handleError (dart:async/future_impl.dart:708:47)
E/flutter ( 9926): #5      Future._propagateToListeners (dart:async/future_impl.dart:729:24)
E/flutter ( 9926): #6      Future._completeWithValue (dart:async/future_impl.dart:529:5)
E/flutter ( 9926): #7      Future._asyncCompleteWithValue.<anonymous closure> (dart:async/future_impl.dart:567:7)
E/flutter ( 9926): #8      _rootRun (dart:async/zone.dart:1190:13)
E/flutter ( 9926): #9      _CustomZone.run (dart:async/zone.dart:1093:19)
E/flutter ( 9926): #10     _CustomZone.runGuarded (dart:async/zone.dart:997:7)
E/flutter ( 9926): #11     _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1037:23)
E/flutter ( 9926): #12     _microtaskLoop (dart:async/schedule_microtask.dart:41:21)
E/flutter ( 9926): #13     _startMicrotaskLoop (dart:async/schedule_microtask.dart:50:5)
E/flutter ( 9926): 

Any ideas what is going on? I'm a little lost on this one. Thanks in advance!

alextekartik commented 4 years ago

Indeed it is weird as it seems to try to access a root file ('/bc_repository.db' which is indeed readonly on most platforms) and it seems to fail after opening the database (or maybe it is failing due to a second call to _db)

For safety, I would ensure that _db is called only once (using a lock or future, here there is an edge case where the database can be opened twice) - add a temp print (with the path) before opening the database for verification.

I'm not sure on which platform you are running the app though (Android? Linux?)

phillwiggins commented 4 years ago

I am using Android in this instance. I will try using a lock now and come back to you shortly. Thanks for the quick response.

phillwiggins commented 4 years ago

Yes, it does look like the path is printed, and I'm assuming the actual file is being created. As you mentioned, I think this might be a concurrent modification issue. Is there any functions I can use to assess wether my db is open already? I currently use a generic db read/write class that multiple repositories extend depending on the child object. Because of this I sometimes have more than one item being saved concurrently. Ideally, I wouldn't need to create a singleton DB, I should be able to check for the db being open and return when it's ready to be used.

alextekartik commented 4 years ago

One quick way to prevent _db to be called twice is to declare it this way:

final Future<Database> _db = () async {
    final appDocDir = await getApplicationDocumentsDirectory();
    await appDocDir.create(recursive: true);
    final path = join(appDocDir.path, _dbKey);
    final db = await _dbFactory.openDatabase(path, version: 1);
    print('TESTDB: ${db.toString()}');
    return db;
  } ();

And no there is no isOpened method, sorry (and anyway it is not alway safe and database object only exists when opened).

The path /data/user/0/com.purewowstudio.bodycal_new/app_flutter/ looks weird for android where it typically starts with /data/data/<package_name> but I could be wrong on that.

phillwiggins commented 4 years ago

I'll give that a go, but again I'm assuming if I have multiple repositories using this database, I would essentially need to create a singleton to store the instance of this DB. I suppose it should still work.

Thakns for your help.

alextekartik commented 4 years ago

Yes a singleton is a reasonable solution!

phillwiggins commented 4 years ago

All sorted! Thanks for your help.

I have a generic database class that I extend for every object that is persistable, which all point to the same DB instance stored in a singleton. It seems to have solved my problem.

Thank you very much for your help!