Skip to content

Commit 321f5d9

Browse files
authored
Merge pull request #1568 from couchbase/issue/1563a
Fixed #1563 - Database deletion failing with 1.4-25
2 parents 9af23ad + 45cb03e commit 321f5d9

4 files changed

Lines changed: 149 additions & 128 deletions

File tree

src/main/java/com/couchbase/lite/Database.java

Lines changed: 80 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -394,23 +394,30 @@ public Replication createPushReplication(URL remote) {
394394
* Deletes the database.
395395
*/
396396
@InterfaceAudience.Public
397-
public void delete() throws CouchbaseLiteException {
398-
if (open.get()) {
399-
if (!close()) {
400-
throw new CouchbaseLiteException("The database was open, and could not be closed",
401-
Status.INTERNAL_SERVER_ERROR);
397+
public synchronized void delete() throws CouchbaseLiteException {
398+
// NOTE: synchronized Manager.lockDatabases to prevent Manager to give the deleting Database
399+
// instance. See also `Manager.getDatabase(String, boolean)`.
400+
synchronized (manager.lockDatabases) {
401+
Log.v(TAG, "Deleting %s", this);
402+
403+
if (open.get()) {
404+
if (!close())
405+
throw new CouchbaseLiteException("The database was open, and could not be closed",
406+
Status.INTERNAL_SERVER_ERROR);
402407
}
403-
}
404-
manager.forgetDatabase(this);
405-
if (!exists()) {
406-
return;
407-
}
408408

409-
File dir = new File(path);
410-
if (!FileDirUtils.deleteRecursive(dir))
411-
throw new CouchbaseLiteException("Was not able to delete the database directory",
412-
Status.INTERNAL_SERVER_ERROR);
409+
manager.forgetDatabase(this);
410+
411+
if (!exists())
412+
return;
413413

414+
File dir = new File(path);
415+
if (!FileDirUtils.deleteRecursive(dir))
416+
throw new CouchbaseLiteException("Was not able to delete the database directory",
417+
Status.INTERNAL_SERVER_ERROR);
418+
419+
Log.v(TAG, "Deleted %s", this);
420+
}
414421
}
415422

416423
/**
@@ -1232,7 +1239,7 @@ public List<Replication> getActiveReplications() {
12321239
}
12331240

12341241
@InterfaceAudience.Private
1235-
public boolean exists() {
1242+
public synchronized boolean exists() {
12361243
return new File(path).exists();
12371244
}
12381245

@@ -1268,7 +1275,7 @@ else if (storageType.equals(Manager.FORESTDB_STORAGE))
12681275
}
12691276

12701277
@InterfaceAudience.Private
1271-
public synchronized void open() throws CouchbaseLiteException {
1278+
public void open() throws CouchbaseLiteException {
12721279
open(manager.getDefaultOptions(name));
12731280
}
12741281

@@ -1450,73 +1457,78 @@ SymmetricKey createSymmetricKey(Object keyOrPassword) throws CouchbaseLiteExcept
14501457
@InterfaceAudience.Public
14511458
public synchronized boolean close() {
14521459
storeRef.await();
1453-
try {
1454-
closing.set(true);
14551460

1456-
if (!open.get()) {
1457-
// Ensure that the database is forgotten:
1458-
manager.forgetDatabase(this);
1459-
return false;
1460-
}
1461+
// NOTE: synchronized Manager.lockDatabases to prevent Manager to give the deleting Database
1462+
// instance. See also `Manager.getDatabase(String, boolean)`.
1463+
synchronized (manager.lockDatabases) {
1464+
try {
1465+
closing.set(true);
14611466

1462-
synchronized (databaseListeners) {
1463-
for (DatabaseListener listener : databaseListeners)
1464-
listener.databaseClosing();
1465-
}
1467+
if (!open.get()) {
1468+
// Ensure that the database is forgotten:
1469+
manager.forgetDatabase(this);
1470+
return false;
1471+
}
14661472

1467-
synchronized (lockViews) {
1468-
if (views != null) {
1469-
for (View view : views.values())
1470-
view.close();
1473+
synchronized (databaseListeners) {
1474+
for (DatabaseListener listener : databaseListeners)
1475+
listener.databaseClosing();
14711476
}
1472-
views = null;
1473-
}
14741477

1475-
// Make all replicators stop and wait:
1476-
boolean stopping = false;
1477-
synchronized (activeReplicators) {
1478-
for (Replication replicator : activeReplicators) {
1479-
if (replicator.getStatus() == Replication.ReplicationStatus.REPLICATION_STOPPED)
1480-
continue;
1481-
replicator.stop();
1482-
stopping = true;
1478+
synchronized (lockViews) {
1479+
if (views != null) {
1480+
for (View view : views.values())
1481+
view.close();
1482+
}
1483+
views = null;
14831484
}
14841485

1485-
// maximum wait time per replicator is 60 sec.
1486-
// total maximum wait time for all replicators is between 60sec and 119 sec.
1487-
long timeout = Replication.DEFAULT_MAX_TIMEOUT_FOR_SHUTDOWN * 1000;
1488-
long startTime = System.currentTimeMillis();
1489-
while (activeReplicators.size() > 0 && stopping &&
1490-
(System.currentTimeMillis() - startTime) < timeout) {
1491-
try {
1492-
activeReplicators.wait(timeout);
1493-
} catch (InterruptedException e) {
1486+
// Make all replicators stop and wait:
1487+
boolean stopping = false;
1488+
synchronized (activeReplicators) {
1489+
for (Replication replicator : activeReplicators) {
1490+
if (replicator.getStatus() == Replication.ReplicationStatus.REPLICATION_STOPPED)
1491+
continue;
1492+
replicator.stop();
1493+
stopping = true;
1494+
}
1495+
1496+
// maximum wait time per replicator is 60 sec.
1497+
// total maximum wait time for all replicators is between 60sec and 119 sec.
1498+
long timeout = Replication.DEFAULT_MAX_TIMEOUT_FOR_SHUTDOWN * 1000;
1499+
long startTime = System.currentTimeMillis();
1500+
while (activeReplicators.size() > 0 && stopping &&
1501+
(System.currentTimeMillis() - startTime) < timeout) {
1502+
try {
1503+
activeReplicators.wait(timeout);
1504+
} catch (InterruptedException e) {
1505+
}
14941506
}
1507+
// clear active replicators:
1508+
activeReplicators.clear();
14951509
}
1496-
// clear active replicators:
1497-
activeReplicators.clear();
1498-
}
14991510

1500-
// Clear all replicators:
1501-
allReplicators.clear();
1511+
// Clear all replicators:
1512+
allReplicators.clear();
15021513

1503-
// cancel purge timer
1504-
cancelPurgeTimer();
1514+
// cancel purge timer
1515+
cancelPurgeTimer();
15051516

1506-
// Close Store:
1507-
if (store != null)
1508-
store.close();
1517+
// Close Store:
1518+
if (store != null)
1519+
store.close();
15091520

1510-
// Clear document cache:
1511-
clearDocumentCache();
1521+
// Clear document cache:
1522+
clearDocumentCache();
15121523

1513-
// Forget database:
1514-
manager.forgetDatabase(this);
1524+
// Forget database:
1525+
manager.forgetDatabase(this);
15151526

1516-
open.set(false);
1517-
return true;
1518-
} finally {
1519-
closing.set(false);
1527+
open.set(false);
1528+
return true;
1529+
} finally {
1530+
closing.set(false);
1531+
}
15201532
}
15211533
}
15221534

src/main/java/com/couchbase/lite/Manager.java

Lines changed: 60 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ public final class Manager {
7777

7878
private ManagerOptions options;
7979
private File directoryFile;
80+
/*package*/ Object lockDatabases = new Object();
8081
private Map<String, Database> databases;
8182
private Map<String, Object> encryptionKeys;
8283
private List<Replication> replications;
@@ -262,24 +263,26 @@ public File getDirectory() {
262263
*/
263264
@InterfaceAudience.Public
264265
public void close() {
265-
Log.d(Database.TAG, "Closing " + this);
266-
// Close all database:
267-
// Snapshot of the current open database to avoid concurrent modification as
268-
// the database will be forgotten (removed from the databases map) when it is closed:
269-
Database[] openDbs = databases.values().toArray(new Database[databases.size()]);
270-
for (Database database : openDbs) {
271-
database.close();
272-
}
273-
databases.clear();
266+
synchronized (lockDatabases) {
267+
Log.d(Database.TAG, "Closing " + this);
268+
269+
// Close all database:
270+
// Snapshot of the current open database to avoid concurrent modification as
271+
// the database will be forgotten (removed from the databases map) when it is closed:
272+
Database[] openDbs = databases.values().toArray(new Database[databases.size()]);
273+
for (Database database : openDbs)
274+
database.close();
275+
databases.clear();
274276

275-
// Stop reachability:
276-
context.getNetworkReachabilityManager().stopListening();
277+
// Stop reachability:
278+
context.getNetworkReachabilityManager().stopListening();
277279

278-
// Shutdown ScheduledExecutorService:
279-
if (workExecutor != null && !workExecutor.isShutdown()) {
280-
Utils.shutdownAndAwaitTermination(workExecutor);
280+
// Shutdown ScheduledExecutorService:
281+
if (workExecutor != null && !workExecutor.isShutdown())
282+
Utils.shutdownAndAwaitTermination(workExecutor);
283+
284+
Log.d(Database.TAG, "Closed " + this);
281285
}
282-
Log.d(Database.TAG, "Closed " + this);
283286
}
284287

285288
/**
@@ -512,7 +515,9 @@ public void setDefaultHttpClientFactory(HttpClientFactory defaultHttpClientFacto
512515
*/
513516
@InterfaceAudience.Private
514517
public Collection<Database> allOpenDatabases() {
515-
return databases.values();
518+
synchronized (lockDatabases) {
519+
return databases.values();
520+
}
516521
}
517522

518523
/**
@@ -551,25 +556,28 @@ public void run() {
551556
* @exclude
552557
*/
553558
@InterfaceAudience.Private
554-
public synchronized Database getDatabase(String name, boolean mustExist) {
555-
if (options.isReadOnly())
556-
mustExist = true;
557-
Database db = databases.get(name);
558-
if (db == null) {
559-
if (!isValidDatabaseName(name))
560-
throw new IllegalArgumentException("Invalid database name: " + name);
561-
String path = pathForDatabaseNamed(name);
562-
if (path == null)
563-
return null;
564-
db = new Database(path, name, this, options.isReadOnly());
565-
if (mustExist && !db.exists()) {
566-
Log.i(Database.TAG, "mustExist is true and db (%s) does not exist", name);
567-
return null;
559+
public Database getDatabase(String name, boolean mustExist) {
560+
synchronized (lockDatabases) {
561+
if (options.isReadOnly())
562+
mustExist = true;
563+
Database db = databases.get(name);
564+
if (db == null) {
565+
if (!isValidDatabaseName(name))
566+
throw new IllegalArgumentException("Invalid database name: " + name);
567+
String path = pathForDatabaseNamed(name);
568+
if (path == null)
569+
return null;
570+
db = new Database(path, name, this, options.isReadOnly());
571+
if (mustExist && !db.exists()) {
572+
Log.i(Database.TAG, "mustExist is true and db (%s) does not exist", name);
573+
return null;
574+
}
575+
db.setName(name);
576+
databases.put(name, db);
568577
}
569-
db.setName(name);
570-
databases.put(name, db);
578+
Log.v(Log.TAG_DATABASE, "getDatabase() %s %s", this, db);
579+
return db;
571580
}
572-
return db;
573581
}
574582

575583
/**
@@ -1003,21 +1011,27 @@ private static Map<String, Object> parseSourceOrTarget(Map<String, Object> prope
10031011
*/
10041012
@InterfaceAudience.Private
10051013
protected void forgetDatabase(Database db) {
1006-
// remove from cached list of dbs
1007-
databases.remove(db.getName());
1008-
1009-
// remove from list of replications
1010-
// TODO: should there be something that actually stops the replication(s) first?
1011-
Iterator<Replication> replicationIterator = this.replications.iterator();
1012-
while (replicationIterator.hasNext()) {
1013-
Replication replication = replicationIterator.next();
1014-
if (replication.getLocalDatabase().getName().equals(db.getName())) {
1015-
replicationIterator.remove();
1014+
synchronized (lockDatabases) {
1015+
Log.v(Log.TAG_DATABASE, "Fogetting forgetDatabase() %s %s", this, db);
1016+
1017+
// remove from cached list of dbs
1018+
databases.remove(db.getName());
1019+
1020+
// remove from list of replications
1021+
// TODO: should there be something that actually stops the replication(s) first?
1022+
Iterator<Replication> replicationIterator = this.replications.iterator();
1023+
while (replicationIterator.hasNext()) {
1024+
Replication replication = replicationIterator.next();
1025+
if (replication.getLocalDatabase().getName().equals(db.getName())) {
1026+
replicationIterator.remove();
1027+
}
10161028
}
1017-
}
10181029

1019-
// Remove registered encryption key if available:
1020-
encryptionKeys.remove(db.getName());
1030+
// Remove registered encryption key if available:
1031+
encryptionKeys.remove(db.getName());
1032+
1033+
Log.v(Log.TAG_DATABASE, "Forgot forgetDatabase() %s %s", this, db);
1034+
}
10211035
}
10221036

10231037
/**

src/main/java/com/couchbase/lite/replicator/ReplicationInternal.java

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -878,17 +878,7 @@ public void onCompletion(Response httpResponse, Object result, Throwable e) {
878878
Map<String, Object> response = (Map<String, Object>) result;
879879
body.put("_rev", response.get("rev"));
880880
remoteCheckpoint = body;
881-
boolean isOpen = false;
882-
try {
883-
if (db != null) {
884-
db.open();
885-
isOpen = true;
886-
}
887-
} catch (CouchbaseLiteException ex) {
888-
Log.w(Log.TAG_SYNC, "%s: Cannot open the database", ex, this);
889-
}
890-
891-
if (isOpen) {
881+
if (db != null && db.isOpen()) {
892882
Log.d(Log.TAG_SYNC,
893883
"%s: saved remote checkpoint, updating local checkpoint. RemoteCheckpoint: %s",
894884
this, remoteCheckpoint);

src/main/java/com/couchbase/lite/router/Router.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,7 @@ public void start() {
622622
connection.getResponseBody() == null &&
623623
connection.getHeaderField("Content-Type") == null &&
624624
dontOverwriteBody == false) {
625+
connection.getResHeader().add("Content-Type", CONTENT_TYPE_JSON);
625626
connection.setResponseBody(new Body("{\"ok\":true}".getBytes()));
626627
}
627628

@@ -1141,13 +1142,17 @@ public Status do_PUT_Database(Database _db, String _docID, String _attachmentNam
11411142
return new Status(Status.CREATED);
11421143
}
11431144

1144-
public Status do_DELETE_Database(Database _db, String _docID, String _attachmentName)
1145-
throws CouchbaseLiteException {
1145+
public Status do_DELETE_Database(Database _db, String _docID, String _attachmentName) {
11461146
if (getQuery("rev") != null) {
11471147
return new Status(Status.BAD_REQUEST);
11481148
// CouchDB checks for this; probably meant to be a document deletion
11491149
}
1150-
db.delete();
1150+
try {
1151+
db.delete();
1152+
} catch (CouchbaseLiteException e) {
1153+
Log.w(TAG, "Failed to delete database: do_DELETE_Database()", e);
1154+
return e.getCBLStatus();
1155+
}
11511156
return new Status(Status.OK);
11521157
}
11531158

0 commit comments

Comments
 (0)