Skip to content

YapDatabase cannot be created twice at same path. #538

@MythicLionMan

Description

@MythicLionMan

It is an API error to create two instances of YapDatbase that reference the same database file. To enforce this YapDatabaseManager registers the normalized path of all database instances, and releases them when the instance is dealloced. When a new instance is created it checks the registry, and if another instance has registered the path then it throws an exception.

There is a bug in this procedure where a registered path can 'leak' and prevent a new instance from being allocated with the same path despite the fact that the original instance has been released.

Steps to reproduce

  1. Create a YapDatabase instance with a path that is relative to '/private'.
  2. Profit from said instance…
  3. Release the instance.
  4. Delete the database files that were created in step 1.
  5. Create a new instance with the same same path and in the same running application as step 1.

Expected behaviour

The second database instance is created and useable.

Actual behaviour

Creation of the second instance fails because the database path was not released in step 4 (the path is still visible in the 'registeredPaths' set).

Analysis

The documentation for 'stringByStandardizingPath' states that it has different behaviour for paths that have '/private' as their root based on the existence of the file reference by the path in the file system. If such a path still points to a valid file after removing the '/private' prefix, the prefix is removed (under the assumption that it is the same file). (Oddly enough the documentation for the Swift version of the method is not so detailed, even though it has the same behaviour).

Thus if Yap registers the normalized path before the database is created, when it is released and the path is normalized again it attempts to deregister a path without the '/private' prefix, while it registered one that does have the prefix, so the registration leaks. The next attempt to connect will succeed if the file remains, since the new normalized path won't have the prefix either. But if the database is deleted the new normalized path will have the prefix, and since it is still registered it will throw an exception.

Workaround

An easy workaround is to 'touch' the database file before creating a YapDatabase instance. If there is no existing database file create an empty file to ensure that normalization is consistent before and after the database are created. sqlite will happily create a database on top of an empty file.

Fix

There are a few ways to fix this.

  • Use the workaround and create an empty file within YapDatabase init before path normalization.
  • 'Hack' the path by stripping the '/private' prefix off manually, or normalize it with a different API that doesn't consult the filesystem. (but this doesn't fix lookup issues, like a path with and without the prefix, and it isn't very future proof since it has to anticipate the behaviour of stringByStandardizingPath which may change in the future).
  • Do not normalize the path again when deregistering the database to ensure that it is consistent with the path used on launch. (but this doesn't fix lookup issues, like a path with and without the prefix).
  • Use a map table instead of a set with the YapDatabase instance as the key (or some other unique value derived from it), to ensure that the path associated with an instance is always the path that is released when it is dealloced (but this doesn't fix lookup issues, like a path with and without the prefix).
  • Don't check for duplicate paths at all since it is a bit error prone.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions