-
Notifications
You must be signed in to change notification settings - Fork 365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
YapDatabase cannot be created twice at same path. #538
Comments
As a side effect of this issue, an app crash happens during
That's because the - (id)initWithURL:(NSURL *)inURL options:(nullable YapDatabaseOptions *)inOptions
{
// Standardize the path.
// This allows for fileReferenceURL's, and non-standard paths to be passed without issue.
NSString *databasePath = [[[inURL filePathURL] path] stringByStandardizingPath];
// Ensure there is only a single database instance per file.
// However, clients may create as many connections as desired.
if (![YapDatabaseManager registerDatabaseForPath:databasePath])
{
YDBLogError(@"Only a single database instance is allowed per file. "
@"For concurrency you create multiple connections from a single database instance.");
return nil;
}
// ... rest of code yet - (void)dealloc
{
YDBLogVerbose(@"Dealloc <%@ %p: databaseName=%@>", [self class], self, [databaseURL lastPathComponent]);
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithCapacity:3];
userInfo[YapDatabaseUrlKey] = self.databaseURL; // in NIL state
userInfo[YapDatabaseUrlWalKey] = self.databaseURL_wal; // CRASHES HERE
userInfo[YapDatabaseUrlShmKey] = self.databaseURL_shm;
// ... rest of code |
+1 Seems when want to access database from NotificationServiceExtension i have simmilar issue because my main app started + extension is running too and need access to db (and it crashing) |
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
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.
The text was updated successfully, but these errors were encountered: