synchronized_lite 1.0.0+2

  • README.md
  • CHANGELOG.md
  • Example
  • Installing
  • Versions
  • new75

synchronized_lite #

A locking mechanism for Dart analogous to Java synchronized blocks. By wrapping asynchronous Dart code with synchronized(), you can ensure mutually-exclusive, sequential execution of otherwise parallel operations.

The design of this package was inspired by package synchronized. In fact, the API provided by synchronized_lite is compatible with synchronized and can be used as a drop-in replacement, except:

  • It does not support reenterant locks;
  • It does not support timeouts.

Motivation #

In some applications, it can be possible to call an asynchronous function repeatedly, which may lead to inconsistent state. The problem gets more complex when several different asynchronous functions change a shared state. I found myself implementing over an over a pattern that involves storing a future of the currently running asynchronous task and waiting on it before initiating another asynchronous task.

I discovered synchronized after implementing my own version and found that my implementation is much simpler. I did performance tests and concluded that it's also faster and uses less memory. I decided to make it available for everyone who doesn't need the bells and whistles of synchronized but wants to enjoy the benefits of synchronized_lite, which are:

  • Simple and clear implementation. You can review the code and understand how it works in a few minutes.
  • Linear complexity and minimal memory/CPU overhead.

This package will work in a Flutter app and can be useful for ensuring sequential I/O operations such as database updates.

Usage #

The example below demonstrates how to ensure that an object is created in the database only once and that incremental save() operations produce a consistent result. Exceptions that occur inside of the synchronized() call get propagated to the caller.

import 'package:synchronized_lite/synchronized_lite.dart';

abstract class FirebasePersistentModel {

  final _saveLock = Lock();
  Map<String, dynamic> _data;
  DocumentReference _docRef;

  Future<DocumentReference> create() async {
    return await _saveLock.synchronized(() async {
      if(_docRef != null)
        return _docRef;
      _data = _createData();
      _docRef = await _collectionRef.add(_data);
      return _docRef;
    });
  }

  Future<bool> save() async {
    return await _saveLock.synchronized<bool>(() async {
      if(_docRef == null)
        throw Exception("Model has not been created");
      Map<String, dynamic> newData = _createData();
      Map<String, dynamic> changes = getChanges(newData, _data);
      if(changes.length == 0)
        return false;
      await _docRef.setData(changes, merge: true);
      _data = newData;
      return true;
    });
  }

  // implementations of _createData(), getChanges() and other details
  // are omitted in this example.
}

Please note that nested synchronized() calls on the same lock will produce a deadlock. Such behavior is precisely what distinguishes non-reenterant locks from reenterant locks, and synchronized_lite locks are non-reenterant:

import 'package:synchronized_lite/synchronized_lite.dart';

main() async {
  var lock = Lock();
  await lock.synchronized(() async {
    await lock.synchronized(() async {
      // This will never be executed
      print("It works!");
    });
  });
}

Another example of using synchronized() is provided in the /example folder, which uses a Lock object as a mixin, making synchronized blocks look more Java-like, if you prefer.

If you still need more details, synchronized package documentation also has a thorough explanation on how synchronized() blocks work.

Implementation #

Below is the entire source code of this package:

library synchronized_lite;

import 'dart:async';

class Lock {

  Future _last;

  Future<T> synchronized<T>(FutureOr<T> func()) async {
    final prev = _last;
    final completer = Completer();
    _last = completer.future;
    if(prev != null)
      await prev;
    try {
      return await func();
    } finally {
      completer.complete();
    }
  }

}

Testing #

I wrote unit tests that ensure synchronized_lite works as expected. I verified that it behaves identically to synchronized by running the same tests against the synchronized implementation. Below is the output:

00:00 +0: Without synchronized(), all incrementers run concurrently
00:00 +1: With synchronized(), all incrementers run sequentially
00:00 +2: Non-async functions work correctly with synchronized()
00:00 +3: Exceptions are propagated
00:00 +4: All tests passed!

[1.0.0+1]

  • Initial release

[1.0.0+2]

  • Code formatting and documentation changes

example/example.dart

import 'package:synchronized_lite/synchronized_lite.dart';

import 'dart:async';

// Using Lock as a mixin to further mimic Java-style synchronized blocks
class SomeActivity with Lock {

  bool _started = false;

  Future<bool> start() async {
    // It's correct to return a Future returned by synchronized()
    return synchronized(() async {
      if(_started)
        return false;
      // perform the start operation
      await Future.delayed(Duration(seconds: 1));
      print("Started");
      _started = true;
      return true;
    });
  }

  Future<void> stop() async {
    // It's also correct to await a synchronized() call before returning
    // It's incorrect to neither await a synchronized() call nor return its Future.
    await synchronized(() async {
      if(!_started)
        return;
      // perform the stop operation
      await Future.delayed(Duration(seconds: 1));
      print("Stopped");
      _started = false;
    });
  }
}

// Prints:
//   Started
//   Stopped
main() async {
  var a = SomeActivity();
  print("Hello");
  a.start();
  a.start();
  a.stop();
  await a.stop();
}

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  synchronized_lite: ^1.0.0+2

2. Install it

You can install packages from the command line:

with pub:


$ pub get

with Flutter:


$ flutter packages get

Alternatively, your editor might support pub get or flutter packages get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:synchronized_lite/synchronized_lite.dart';
  
Version Uploaded Documentation Archive
1.0.0+2 Jan 27, 2019 Go to the documentation of synchronized_lite 1.0.0+2 Download synchronized_lite 1.0.0+2 archive
1.0.0+1 Jan 26, 2019 Go to the documentation of synchronized_lite 1.0.0+1 Download synchronized_lite 1.0.0+1 archive
Popularity:
Describes how popular the package is relative to other packages. [more]
51
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
100
Overall:
Weighted score of the above. [more]
75
Learn more about scoring.

We analyzed this package on Feb 20, 2019, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.1.0
  • pana: 0.12.13+1

Platforms

Detected platforms: Flutter, web, other

No platform restriction found in primary library package:synchronized_lite/synchronized_lite.dart.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.0.0 <3.0.0
Dev dependencies
synchronized any
test any