@@ -1014,9 +1014,7 @@ public Status do_DELETE_Database(Database _db, String _docID, String _attachment
10141014 if (getQuery ("rev" ) != null ) {
10151015 return new Status (Status .BAD_REQUEST ); // CouchDB checks for this; probably meant to be a document deletion
10161016 }
1017- synchronized (db ) {
1018- db .delete ();
1019- }
1017+ db .delete ();
10201018 return new Status (Status .OK );
10211019 }
10221020
@@ -1176,6 +1174,8 @@ public Status do_POST_Document_bulk_docs(Database _db, String _docID, String _at
11761174
11771175 final Status status = new Status (Status .OK );
11781176 final List <Map <String , Object >> results = new ArrayList <Map <String , Object >>();
1177+ // Transaction provide synchronized feature by SQLiteDatabase.
1178+ // In the transaction block, should not use `synchronized` block
11791179 boolean ret = db .getStore ().runInTransaction (new TransactionalTask () {
11801180 @ Override
11811181 public boolean run () {
@@ -1190,7 +1190,6 @@ public boolean run() {
11901190 if (noNewEdits ) {
11911191 rev = new RevisionInternal (docBody );
11921192 if (rev .getRevID () == null || rev .getDocID () == null || !rev .getDocID ().equals (docID )) {
1193- //status = new Status(Status.BAD_REQUEST);
11941193 status .setCode (Status .BAD_REQUEST );
11951194 } else {
11961195 List <String > history = Database .parseCouchDBRevisionHistory (doc );
@@ -1210,7 +1209,6 @@ public boolean run() {
12101209 result .put ("rev" , rev .getRevID ());
12111210 }
12121211 } else if (allOrNothing ) {
1213- //return status; // all_or_nothing backs out if there's any error
12141212 return false ;
12151213 } else if (status .getCode () == Status .FORBIDDEN ) {
12161214 result = new HashMap <String , Object >();
@@ -1264,6 +1262,7 @@ public Status do_POST_Document_revs_diff(Database _db, String _docID, String _at
12641262
12651263 // Look them up, removing the existing ones from revs:
12661264 try {
1265+ // query db only, not necessary to be syncrhonized
12671266 db .findMissingRevisions (revs );
12681267 } catch (SQLException e ) {
12691268 Log .e (Log .TAG_ROUTER , "Exception" , e );
@@ -1301,6 +1300,7 @@ public Status do_POST_Document_revs_diff(Database _db, String _docID, String _at
13011300 public Status do_POST_Document_compact (Database _db , String _docID , String _attachmentName ) {
13021301 Status status = new Status (Status .OK );
13031302 try {
1303+ // Make Database.compact() thread-safe
13041304 _db .compact ();
13051305 } catch (CouchbaseLiteException e ) {
13061306 status = e .getCBLStatus ();
@@ -1341,6 +1341,7 @@ public Status do_POST_Document_purge(Database _db, String ignored1, String ignor
13411341 Future future = db .runAsync (new AsyncTask () {
13421342 @ Override
13431343 public void run (Database database ) {
1344+ // purgeRevisions uses transaction internally.
13441345 Map <String , Object > purgedRevisions = db .purgeRevisions (docsToRevs );
13451346 asyncApiCallResponse .add (purgedRevisions );
13461347 }
@@ -1563,6 +1564,7 @@ public Status do_GET_Document_changes(Database _db, String docID, String _attach
15631564 Log .v (Log .TAG_ROUTER , "Filter params=" + changesFilterParams );
15641565 }
15651566
1567+ // changesSince() is query only. not required synchronized
15661568 RevisionList changes = db .changesSince (since , options , changesFilter , changesFilterParams );
15671569
15681570 if (changes == null ) {
@@ -1623,27 +1625,14 @@ public Status do_GET_Document(Database _db, String docID, String _attachmentName
16231625 String revID = getQuery ("rev" ); // often null
16241626 RevisionInternal rev = null ;
16251627 if (isLocalDoc ) {
1628+ // query only -> not required synchronized
16261629 rev = db .getLocalDocument (docID , revID );
16271630 } else {
1628- /*
1629- rev = db.getDocument(docID, revID, options);
1630- // Handle ?atts_since query by stubbing out older attachments:
1631- //?atts_since parameter - value is a (URL-encoded) JSON array of one or more revision IDs.
1632- // The response will include the content of only those attachments that changed since the given revision(s).
1633- //(You can ask for this either in the default JSON or as multipart/related, as previously described.)
1634- List<String> attsSince = (List<String>) getJSONQuery("atts_since");
1635- if (attsSince != null) {
1636- String ancestorId = db.findCommonAncestorOf(rev, attsSince);
1637- if (ancestorId != null) {
1638- int generation = RevisionInternal.generationFromRevID(ancestorId);
1639- db.stubOutAttachmentsIn(rev, generation + 1);
1640- }
1641- }
1642- */
16431631 boolean includeAttachments = options .contains (TDContentOptions .TDIncludeAttachments );
16441632 if (includeAttachments ) {
16451633 options .remove (TDContentOptions .TDIncludeAttachments );
16461634 }
1635+ // query only -> not required synchronized
16471636 rev = db .getDocument (docID , revID , true );
16481637 if (rev != null ) {
16491638 rev = applyOptionsToRevision (options , rev );
@@ -1658,8 +1647,6 @@ public Status do_GET_Document(Database _db, String docID, String _attachmentName
16581647 if (cacheWithEtag (rev .getRevID ()))
16591648 return new Status (Status .NOT_MODIFIED ); // set ETag and check conditional GET
16601649
1661- // TODO
1662-
16631650 connection .setResponseBody (rev .getBody ());
16641651 } else {
16651652 List <Map <String , Object >> result = null ;
@@ -1670,6 +1657,7 @@ public Status do_GET_Document(Database _db, String docID, String _attachmentName
16701657 for (RevisionInternal rev : allRevs ) {
16711658
16721659 try {
1660+ // loadRevisionBody is synchronized with store instance.
16731661 db .loadRevisionBody (rev );
16741662 } catch (CouchbaseLiteException e ) {
16751663 if (e .getCBLStatus ().getCode () != Status .INTERNAL_SERVER_ERROR ) {
@@ -1807,14 +1795,14 @@ public Status do_GET_Attachment(Database _db, String docID, String _attachmentNa
18071795 return new Status (Status .NOT_MODIFIED ); // set ETag and check conditional GET
18081796 }
18091797
1810- String type = null ;
18111798 String acceptEncoding = connection .getRequestProperty ("accept-encoding" );
1799+ // getAttachment is safe. this could be static method??
18121800 AttachmentInternal attachment = db .getAttachment (rev , _attachmentName );
18131801 if (attachment == null ) {
18141802 return new Status (Status .NOT_FOUND );
18151803 }
18161804
1817- type = attachment .getContentType ();
1805+ String type = attachment .getContentType ();
18181806 if (type != null ) {
18191807 connection .getResHeader ().add ("Content-Type" , type );
18201808 }
@@ -1850,7 +1838,7 @@ private RevisionInternal update(Database _db, String docID, Body body, boolean d
18501838 }
18511839
18521840 boolean isLocalDoc = docID != null && docID .startsWith (("_local" ));
1853- String prevRevID = null ;
1841+ String prevRevID ;
18541842
18551843 if (!deleting ) {
18561844 Boolean deletingBoolean = (Boolean ) body .getPropertyForKey ("_deleted" );
@@ -1887,19 +1875,46 @@ private RevisionInternal update(Database _db, String docID, Body body, boolean d
18871875
18881876 RevisionInternal result = null ;
18891877 try {
1890- synchronized (_db ) {
1891- if (isLocalDoc ) {
1892- result = _db .getStore ().putLocalRevision (rev , prevRevID , true );
1893- } else {
1894- result = _db .putRevision (rev , prevRevID , allowConflict );
1878+ if (isLocalDoc ) {
1879+ // NOTE: putLocalRevision() does not use transaction internally with obeyMVCC=true
1880+
1881+ final Database fDb = _db ;
1882+ final RevisionInternal _rev = rev ;
1883+ final String _prevRevID = prevRevID ;
1884+ final List <RevisionInternal > _revs = new ArrayList <RevisionInternal >();
1885+ try {
1886+ fDb .getStore ().runInTransaction (new TransactionalTask () {
1887+ @ Override
1888+ public boolean run () {
1889+ try {
1890+ RevisionInternal r = fDb .getStore ().putLocalRevision (_rev , _prevRevID , true );
1891+ _revs .add (r );
1892+ return true ;
1893+ } catch (CouchbaseLiteException e ) {
1894+ throw new RuntimeException (e );
1895+ }
1896+ }
1897+ });
1898+ // success
1899+ if (_revs .size () > 0 )
1900+ result = _revs .get (0 );
1901+ } catch (RuntimeException ex ) {
1902+ if (ex .getCause () != null &&
1903+ ex .getCause ().getCause () != null &&
1904+ ex .getCause ().getCause () instanceof CouchbaseLiteException )
1905+ throw (CouchbaseLiteException ) ex .getCause ().getCause ();
1906+ else
1907+ throw new CouchbaseLiteException (ex , Status .INTERNAL_SERVER_ERROR );
18951908 }
1909+ } else {
1910+ // putRevision() uses transaction internally.
1911+ result = _db .putRevision (rev , prevRevID , allowConflict );
18961912 }
18971913 if (deleting ) {
18981914 outStatus .setCode (Status .OK );
18991915 } else {
19001916 outStatus .setCode (Status .CREATED );
19011917 }
1902-
19031918 } catch (CouchbaseLiteException e ) {
19041919 if (e .getCBLStatus () != null && e .getCBLStatus ().getCode () == Status .CONFLICT ) {
19051920 // conflict is not critical error for replicators, not print stack trace
@@ -1984,6 +1999,7 @@ public Status do_PUT_Document(Database _db, String docID, String _attachmentName
19841999 throw new CouchbaseLiteException (Status .BAD_REQUEST );
19852000 }
19862001 List <String > history = Database .parseCouchDBRevisionHistory (body .getProperties ());
2002+ // forceInsert uses transaction internally, not necessary to apply synchronized
19872003 db .forceInsert (rev , history , source );
19882004 }
19892005 return status ;
@@ -2014,17 +2030,15 @@ private Status updateAttachment(String attachment,
20142030 throw new CouchbaseLiteException (e .getCause (), Status .BAD_ATTACHMENT );
20152031 }
20162032
2017- RevisionInternal rev ;
2018- synchronized (db ) {
2019- rev = db .updateAttachment (
2020- attachment ,
2021- body ,
2022- connection .getRequestProperty ("content-type" ),
2023- AttachmentInternal .AttachmentEncoding .AttachmentEncodingNone ,
2024- docID ,
2025- revID ,
2026- null );
2027- }
2033+ // updateAttachment uses transaction internally, not necessary to be synchronized
2034+ RevisionInternal rev = db .updateAttachment (
2035+ attachment ,
2036+ body ,
2037+ connection .getRequestProperty ("content-type" ),
2038+ AttachmentInternal .AttachmentEncoding .AttachmentEncodingNone ,
2039+ docID ,
2040+ revID ,
2041+ null );
20282042 Map <String , Object > resultDict = new HashMap <String , Object >();
20292043 resultDict .put ("ok" , true );
20302044 resultDict .put ("id" , rev .getDocID ());
@@ -2085,6 +2099,7 @@ private View compileView(String viewName, Map<String, Object> viewProps) {
20852099
20862100 private Status queryDesignDoc (String designDoc , String viewName , List <Object > keys ) throws CouchbaseLiteException {
20872101 String tdViewName = String .format ("%s/%s" , designDoc , viewName );
2102+ // getExistingView is not thread-safe, but not access to db. In the database, it should protect instance variable
20882103 View view = db .getExistingView (tdViewName );
20892104 if (view == null || view .getMap () == null ) {
20902105 // No TouchDB view is defined, or it hasn't had a map block assigned;
@@ -2119,9 +2134,8 @@ private Status queryDesignDoc(String designDoc, String viewName, List<Object> ke
21192134 options .setKeys (keys );
21202135 }
21212136
2122- synchronized (db ) {
2123- view .updateIndex ();
2124- }
2137+ // updateIndex() uses transaction internally, not necessary to apply syncrhonized.
2138+ view .updateIndex ();
21252139
21262140 long lastSequenceIndexed = view .getLastSequenceIndexed ();
21272141
0 commit comments