Knowledge Base
Troubleshooting
this guide covers the most commonly encountered issues using ditto refer to any of the following to help you get back on track if you need technical support, submit a technical support request ticket (see docid\ n4civtbqal1smgjno8am8 ) /#obtaining and analyzing the debug logs /#synchronization seems slow or comes to a halt over time /#app is using too much memory why /#debugging blocked transactions /#causes of blocked transactions /#still stuck obtaining and analyzing the debug logs to troubleshoot a peer to peer data sync issue, first determine the cause of the issue by obtaining and analyzing the database error and warning messages captured in the debug logs setting the logs to debug level as demonstrated in the following snippet, obtain the database debug logs before ditto is initialized so that any potential issues related to the file system access and authentication background tasks that run during initialization are tracked you want to begin gathering logs before ditto is initialized in order to capture any potential issues with those two background tasks dittologger minimumloglevel = debug let ditto = ditto(identity onlineplayground( appid "replace me with your app id", token "replace me with your playground token" )) do { try ditto startsync() } catch (let err) { print(err localizeddescription) }try { val androiddependencies = defaultandroiddittodependencies(context) val identity = dittoidentity onlineplayground( androiddependencies, appid = "replace me with your app id", token = "replace me with your playground token" ) dittologger minimumloglevel = dittologlevel debug ditto = ditto(androiddependencies, identity) ditto startsync() } catch (e dittoerror) { log e("ditto error", e message!!) }import { init, ditto, logger } from "@dittolive/ditto" // set the ditto log level to debug logger minimumloglevel = 'debug' const identity identity = { type 'onlineplayground', appid 'replace me with your app id', token 'replace me with your playground token' } const ditto = new ditto(identity, ' /your ditto application data path') ditto startsync()dittodependencies androiddependencies = new defaultandroiddittodependencies(this context); // set the ditto min log level to debug dittologger setminimumloglevel(dittologlevel debug); dittoidentity identity = new dittoidentity onlineplayground(androiddependencies, "replace me with your app id", "your playground token here"); ditto ditto = new ditto(androiddependencies, identity); try { ditto startsync(); } catch(dittoerror e) { //handle error }try { dittologger setminimumloglevel(dittologlevel debug); var ditto = new ditto(dittoidentity onlineplayground("replace me with your app id", "replace me with your playground token", true), path); ditto startsync(); } catch (dittoexception ex) { console writeline($"ditto error {ex message}"); }try { dittologger setminimumloglevel(dittologlevel debug); var ditto = new ditto(dittoidentity onlineplayground("replace me with your app id", "replace me with your playground token", true), path); ditto startsync(); } catch (dittoexception ex) { console writeline($"ditto error {ex message}"); }let ditto = ditto builder() // creates a `ditto data` folder in the directory containing the executing process with root(arc new(persistentroot from current exe()?)) with identity(|ditto root| { // provided as an env var, may also be provided as hardcoded string let app id = appid from env("replace me with your app id")?; let shared token = std env var("replace me with a shared token") unwrap(); let enable cloud sync = true; let custom auth url = none; onlineplayground new( ditto root, app id, shared token, enable cloud sync, custom auth url, ) })? with minimum log level(loglevel debug) build()?; ditto start sync()?; authentication for onlineplayground and onlinewithauthentication identities, the first thing that ditto needs to do is authenticate to the big peer confirm that all devices share the same appid by verifying that the app id log line contains your appid ditto new from uninit ditto; app id = 22389e28 9590 4cbf b683 b3ac5ab2269e; site id = 13769592724050309105; history tracking = disabled; presence broadcast targets = smallpeersonly once confirmed, verify that authentication was successful authclient authentication request succeeded once authentication is successful, client re authentication does not occur until the device's local certificate expires, as indicated by the following authclient refreshing token in 302399 seconds incorrect app id if the appid or token are incorrect, the following displays in the debug logs authclient failed to get json for auth response; e = reqwest error { kind decode, source error("expected value", line 1, column 1) authclient no valid web token present can not request certificate authclient failed to obtain a certificate valuenotfound see if the following fixes your issue log in to the https //portal ditto live go to your app copy the appid and playground token and use it in your ditto initialization outdated certificate \[debug] 2023 08 03t17 36 35 046z failed to connect to peer; error = connect failed because a tls stream couldn't be established invalid peer certificate contents invalid peer certificate unknownissuer; remote peer = multicastremotepeer(id 10, announce q2rlcgeydplwjfditto) if you see the above message, y our device's locally cached certificate is invalid the device needs to call ditto auth logout() and reconnect to the internet to get a new certificate alternatively, you can clear the local cache by reinstalling the mobile application or clearing the local persistence directory https //docs ditto live/ios/common/troubleshooting#outdated certificate authentication server unavailable error attempting authentication with token could not connect to authentication server error attempting authentication with token failed to authenticate internal server error; debug some("ditto cloud failed to decode the response body this is most likely due to malformed json in the response error decoding response body expected value at line 1 column 1"); client info none if you see either of the above messages, t his is most likely a problem with your authentication webhook there is a debugging level log line that will provide more information about the authentication webhook if you need more information send a post request with a json stringified body to your server to ensure that the aws hosted big peer servers located in u s regions successfully send post requests to your authentication server curl x post https //my webhook com ditto closing prematurely if you see that ditto is unable to sync, and see the log lines below, it's possible that your ditto instance is being deallocated before it is able to synchronize with other peers ensure that the ditto instance is initialized and saved in a global variable that is long lived for the duration of the application \[debug] 2024 06 06t23 45 29 634z shutting down ditto \[info] 2024 06 06t23 45 29 634z peer shutting down \[debug] 2024 06 06t23 45 29 634z stopping http server \[debug] 2024 06 06t23 45 29 634z stopping tcp server \[info] 2024 06 06t23 45 29 634z peer shutdown done \[debug] 2024 06 06t23 45 29 634z ditto shutdown complete \[debug] 2024 06 06t23 45 29 634z deallocating ditto syncing with big peer a small peer sync with the big peer using a websocket connection the debug logs will tell you each step to create a successful websocket connection to the big peer first, the peer will discover the big peer and you'll see a discovered event peer event = discovered(websocketclientpeer(websocketclientremotepeer { id 4, transport id 3, local announce localannouncedata { outer protocol version 50, os generic, network id 2383820004, query overlap group none, device name "mozilla/5 0 (macintosh; intel mac os x 10 15 7) applewebkit/537" }, connect address "wss\ //22389e28 9590 4cbf b683 b3ac5ab2269e cloud ditto live", routing hint routinghint(0), condition sender unboundedsender { chan tx { inner chan { tx tx { block tail 0x242120, tail position 0 }, semaphore semaphore(0), rx waker atomicwaker, tx count 2, rx fields " " } } } })) if the websocket connection that links the small peer to the big peer is successful, the debug logs contain the connectionestablished event peer event = connectionestablished(peerconnection { id 4, connection type wsclient, remote announce announce { outer protocol version some('2'), os some(linux), network id some(1356909299), query overlap group none, device name some("ditto cloud") }, remote peer id siteidandpubkey(\[ ]) if you see streamfailed or connectionended or any errors related to websocket connection with the big peer, there is likely an error in the big peer subscription server for troubleshooting help, contact ditto support corrupted certificate if your certificate chain is corrupted, you will see the following connect failed because the underlying websocket transport reported an error tls error webpki error unsupportedcriticalextensio to fix this, please try to reset your certificate chain on macos, open the keychain acess application and navigate to the settings, and click "reset default keychains " keychain app > settings > reset default keychains you will then see that the underlying physical replication session has been started with phy started the pksome bytes identifer displays the public key to the big peer instance that a small peer websocket connection has temporary sync access note that the following snippet is merely an example; the pksome bytes identifer is not a guaranteed static variable phy started; remote = pkaoccgkmch0wd6y83qm2uhtin6264jivvimg6xdr778odakodfkw once temporary remote access is authorized, the following debug log message appears to indicate that the small peer (client) websocket connection to the big peer (cloud server) is successful virtconnelectednewconn { new conn some((4, wsclient)), old conn none } permissions and subscriptions for the small peer to have an active subscription to read and write to other peers, it must first be authorized access by a big peer if the small peer does not have permissions to access database replication, it cannot subscribe to its collections to read and write permissions given this, before you check the small peer's active read write subscriptions, confirm that the small peer has permissions to access the database local permission permission { read permissionrules { everything true, queries by collection {} }, write permissionrules { everything true, queries by collection {} } }, remote permission permission { read permissionrules { everything true, queries by collection {} }, write permissionrules { everything true, queries by collection {} } } local permissions refer to the current peer that is running on the device once the peer is authorized, it will begin to print the active subscriptions ditto will print the active subscriptions every time the sync engine wakes up this includes a local write to the database or a sync event from another peer writing to their local database you will want to verify that these permissions are correct and what you expect a peer cannot subscribe to a collection if it does not have read permission the default big peer remote permission to access a connected small peer local database replication to read and write is as follows write { everything true, queries by collection {} } read { everything true, queries by collection {} } subscriptions now that default permissions are verified, confirm that the big peer is connected to a small peer and the local to global database replication task is running ditto prints a list of all the queries that the peer is currently subscribed to local subscription subscription { everything false, queries {" presence" {query { expr " id != '13407763363308666127' && v == 3 && t > 1682717143093", order by \[], limit none, offset none }}, "tasks" {query { expr "isdeleted == false", order by \[], limit none, offset none }}} }, remote subscription subscription { everything true, queries {} } by default, each peer includes some internal subscriptions, which are denoted using a double underscore ( ) before the collection name; for example, the following default internal presence subscription {" presence" { expr " id != '13407763363308666127' && v == 3 && t > 1682717143093", order by \[], limit none, offset none }} you will also see a list of remote subscriptions the big peer subscribes to everything, so you will see the following line which references the big peer remote subscription subscription { everything true, queries {} } if there are any application level subscriptions, they will be listed by collection for instance, if a small peer that subscribes to all tasks that are not deleted, the following appears "tasks" {query { expr "isdeleted == false", order by \[], limit none, offset none }}} if a subscription changes, the following appears application notifying a local subscription change if you have problems with subscriptions or permssions, you can try the operations with global read and write permission to verify that sync succeeds in this case you can do this by using an onlineplayground identity, which defaults to global read and write permissions check the following do your permissions match your subscriptions? if you do not have permission to subscribe to data, it will not sync to the device are you subscribing to what you expect to see? do you have more subscriptions than you expect to have? do your live queries match the subscriptions? synchronization seems slow, or comes to a halt over time https //legacydocs ditto live/javascript/common/troubleshooting#synchronization seems slow or comes to a halt over time ensure that you are only creating a fixed number of live queries, with a smaller amount of data evict irrelevant data you can evict all irrelevant once per day turn off verbose logging verbose logging can slow down replication considerably, especially with thousands of documents hence, it could look like that sync is stalling, but that can be indistinguishable from the logging mechanism slowing down ditto because it is trying to write too many lines look at the size of your ditto directory is it very large? large databases will be slower try to query less data query parsing errors ditto store collection("mycollection") find("") subscribe() // > parseerrorditto store collection("mycollection") find("") subscribe() // > parseerrorditto store collection("mycollection") find("") subscribe() // > parseerrorditto store collection("mycollection") find("") subscribe() // > parseerrorditto store collection("mycollection") find("") subscribe() // > parseerrorditto store collection("mycollection") find("") subscribe() // > parseerror parseerror can be printed in the debug logs when there is a problem with your query for example, if you create a subscription in swift that is the empty string, you will see a parseerror in the logs writes if a write is made to the local database, the following debugging messages appear write txn committed; txn id = 40; originator = user notifying a database change; transaction = 40 once a write is made to the local database, ditto re prints the active subscriptions and permissions to access the debug log, as demonstrated in the previous snippet next, the small peer creates the "update file " annotated in the debug logs as follows, the update file provides the status of the data update and, using the pka and pkb identifiers, indicates the two peers involved in the data exchange creating a sending update, sending update path = "pka/pkb/sending update" once the local peer finishes creating the update file, the following appears to indicate that the update file is complete and the local peer is ready to send the new update to the remote peer note that at this time the local write has yet to be synchronized across all connected peers update creation done; since index = 40; new update created = true; num docs = 1; elapsed = 908us 708ns if the local peer has received the update, all connected peers are synchronized, and the local database replication process is complete, the following appears no next update chunk to send setting is ready to false no message to send did your device connect to the internet? onlineplayground applications must connect to the big peer first before going offline do you have a firewall or proxy enabled that is blocking ditto's connection to the big peer? a proxy may either either block connections or cause errors in the log by substituting its own tls certificate invalid certificate unknownissuer if you see this log message you will either need to get ditto unblocked or add the ca certificate to the small peer's trusted certificate store verify that you can reach the following endpoints you should see the output exactly as written below \> nc v my app id cloud ditto live 443 connection to my app id cloud ditto live port 443 \[tcp/https] succeeded! ^c \> curl i https //my app id cloud ditto live/ ditto/auth/login http/1 1 405 method not allowed date fri, 30 sep 2022 02 03 36 gmt content type text/plain; charset=utf 8 content length 23 connection keep alive access control allow origin access control allow credentials true access control allow methods get, put, post, delete, patch, options access control allow headers x ditto ensure insert,x hydra ensure insert,x ditto client id,x hydra client id,accept,referer,keep alive,user agent,x requested with,if modified since,cache control,content type,authorization,x forwarded for access control max age 1728000 http method not allowed% if this test passes, next check to see if websockets are blocked on your machine some corporate networks, firewalls, or proxies block the http upgrade packet that tells the websocket server to keep the connection alive check with your it administrator to see if your computer is configured to block websocket connections online with authentication did you follow the ? the tutorial is your best guide for implementing this correctly see the https //github com/getditto/examples permission common mistakes authenticationexpiringsoon and authenticationrequired both need to be implemented according to the since callback objects are only invoked when ditto initializes and the client authentication certificate expires, do not create subscriptions inside callbacks keep a strong reference to the authclient for the duration of the ditto object, otherwise the auth handler will become garbage collected at an inappropriate time verify that your webhook provider name is the provider name given to the ditto client must match a provider name in the portal (e g , my auth ) is your authclient becoming garbage collected? https //legacydocs ditto live/javascript/common/troubleshooting#is your authclient becoming garbage collected ensure that you keep a reference to the authclient in scope for the duration that ditto is also active you should attach the dittoauth variable to the object so it does not get garbage collected for example namespace sync { public class dittoclient { private ditto ditto; \+ private dittoauthdelegate dittoauthdelegate; public dittoclient(string appid, string id, string jwt) { \+ dittoauthdelegate = new dittoauthdelegate(id, jwt); ditto = new ditto(identity); } } } connection issues if you are unable to see connections over particular transports, first ensure you are following the sdk setup guide for your language docid\ aoqcvpfwopferj ynn6xf docid\ es2xjnhux53abacyybd6r docid\ k dnsq3kspebc21vasdsu docid\ gpnhy 1 kdnqahphcoxfi after you verify that your app is set up correctly, if connection issues continue follow these debugging steps bluetooth https //legacydocs ditto live/javascript/common/troubleshooting#bluetooth turn use location on turn bluetooth scanning on are permissions set correctly? see go to your os level permissions for bluetooth and clear the app permissions for your application delete the app, install it again, and open it does it ask for bluetooth permissions? apple wireless direct link, p2p wifi, wifi aware https //legacydocs ditto live/javascript/common/troubleshooting#apple wireless direct link p2p wifi wifi aware go to your os level permissions and clear the app permissions for your application delete the app, install it again, and open it does it ask for location permissions? if you are using a custom local area network (lan) https //legacydocs ditto live/javascript/common/troubleshooting#local area network lan are permissions set correctly? see are both devices connected to the same wifi network? check your router settings and see the if your device has vpn enabled, then peers can fail to communicate ensure that your vpn supports udp multicasting app is using too much memory, why? https //legacydocs ditto live/javascript/common/troubleshooting#app is using too much memory why use profiling tools for your platform to better understand where the memory leak may be occurring a common issue we see in reactive apps is a failure to dispose of resources as conditions change your app could create a large accumulation of publishers that infinitely grow every livequery and subscription in ditto must be explicitly stopped using the stop or cancel api see https //legacydocs ditto live/javascript/common/concepts/syncing data for more information debugging blocked transactions https //legacydocs ditto live/javascript/common/troubleshooting#debugging blocked transactions this section only discusses blocked transactions on native platforms (e g ios, android, windows, linux) ditto in web browsers operates sufficiently differently and isn’t covered here blocked write transactions will automatically retry until they succeed a blocked write transaction will never crash howewever, blocked write transactions are a common cause for poor database performance long running blocks are generally bad since they mean that nothing else can be writing to the database during this time this could manifest itself as one of many problems unresponsive ui an interaction might try to update a document, but is blocked by an existing write transaction slow sync new updates cannot be integrated into the store, since they’re blocked by another write transaction a blocked write transaction can hint that something is wrong with the application code, or at a deeper level in ditto this page contains some tips & tricks to help understand the situation the process of unblocking is automatic and you don’t need to write any code to handle this however, you can drastically reduce the chance of blocking transactions by making sure a device is only syncing the data it really needs did your certificate expire and fail to refresh? when using onlinewithauthentication , send a post request with a json stringified body to your server to ensure that the aws hosted big peer servers located in u s regions successfully send post requests to your authentication server authenticationexpiringsoon since callback objects are only invoked when ditto initializes and the client authentication certificate expires, do not create subscriptions inside callbacks keep a strong reference to the authclient for the duration of the ditto object, otherwise the auth handler will become garbage collected at an inappropriate time verify that your webhook provider name is https //legacydocs ditto live/javascript/common/security/authentication#login https //legacydocs ditto live/javascript/common/troubleshooting#verify that your webhook provider name is correctly copied in the ditto portal the provider name given to the ditto client must match a provider name in the portal (e g , my auth ) is your authclient becoming garbage collected? ensure that you keep a reference to the authclient in scope for the duration that ditto is also active you should attach the dittoauth variable to the object so it does not get garbage collected for example psuedocode namespace sync { public class dittoclient { private ditto ditto; \+ private dittoauthdelegate dittoauthdelegate; public dittoclient(string appid, string id, string jwt) { \+ dittoauthdelegate = new dittoauthdelegate(id, jwt); ditto = new ditto(identity); } } } bluetooth turn use location on turn bluetooth scanning on are permissions set correctly? from docid\ qhviwdwaivofdug u5zov , refer to the installation article for your language) go to your os level permissions for bluetooth and clear the app permissions for your application delete the app, install it again, and open it does it ask for bluetooth permissions? android only are you calling ditto refreshpermissions() ? apple wireless direct link, p2p wifi, wifi aware are permissions set correctly? (from docid\ qhviwdwaivofdug u5zov , refer to the installation article for your language) go to your os level permissions and clear the app permissions for your application delete the app, install it again, and open it does it ask for location permissions? if you are using a custom transportconfig , make sure you have enabled all peer to peer transports using enableallpeertopeer() local area network (lan) are permissions set correctly? from docid\ qhviwdwaivofdug u5zov , refer to the installation article for your language) are both devices connected to the same wifi network? check your router settings and see docid\ bnu17t7dnclapggfx32a6 if your device has vpn enabled, then peers can fail to communicate ensure that your vpn supports udp multicasting synchronization seems slow, or comes to a halt over time ensure that you are only creating a fixed number of live queries, with a smaller amount of data do not use findall() because you can sync more data than the small peer can reasonably handle evict irrelevant data you can evict all irrelevant once per day turn off verbose logging verbose logging can slow down replication considerably, especially with thousands of documents hence, it could look like that sync is stalling, but that can be indistinguishable from the logging mechanism slowing down ditto because it is trying to write too many lines look at the size of your ditto directory is it very large? large databases will be slower try to query less data app is using too much memory, why? use profiling tools for your platform to better understand where the memory leak may be occurring a common issue we see in reactive apps is a failure to dispose of resources as conditions change your app could create a large accumulation of publishers that infinitely grow every livequery and subscription in ditto must be explicitly stopped using the stop or cancel api see for more information debugging blocked transactions this section only discusses blocked transactions on native platforms (e g ios, android, windows, linux) ditto in web browsers operates sufficiently differently and isn’t covered here blocked write transactions will automatically retry until they succeed a blocked write transaction will never crash howewever, blocked write transactions are a common cause for poor database performance long running blocks are generally bad since they mean that nothing else can be writing to the database during this time this could manifest itself as one of many problems unresponsive ui an interaction might try to update a document, but is blocked by an existing write transaction slow sync new updates cannot be integrated into the store, since they’re blocked by another write transaction a blocked write transaction can hint that something is wrong with the application code, or at a deeper level in ditto this page contains some tips & tricks to help understand the situation the process of unblocking is automatic and you don’t need to write any code to handle this however, you can drastically reduce the chance of blocking transactions by making sure a device is only syncing the data it really needs what is a “blocked” transaction? https //docs ditto live/ios/common/troubleshooting#what is a blocked transaction at any given time, there can be only one write transaction any subsequent attempts to open another write transaction will become blocked until the existing write transaction finishes read vs write transactions https //docs ditto live/ios/common/troubleshooting#read vs write transactions read transactions operate differently to write transactions read transactions cannot be blocked and can run in parallel with write transactions read transactions don’t block each other, don’t block write transactions, and aren’t blocked by write transactions if a write transaction is blocked, ditto will log a message with increasing severity every 10s time (t) a transaction has been blocked log level t ≤ 30s debug 31s < t ≤ 120s warn 120s < t error to see these logs in the database, it’s important to have a minimum log level set transactions that are blocked for over 2 minutes should always be visible in the logs if info level is used, then info , warn , and error messages will all be included in the logs this means any write transactions blocking for more than 30s should always be visible in the logs reading the logs https //docs ditto live/ios/common/troubleshooting#reading the logs if a write transaction is blocked, the device logs will look something like the following in this example we can see a write transaction was blocked for a total of 150s (or 2 5 minutes) as time progressed, ditto complained more and more loudly (starting with debug logs before eventually logging at error level) eventually the existing transaction finished and blocked transaction was was able to proceed the write transaction which was blocked was for a ditto internal component this is identified by “originator=internal” the existing, long running write transaction which was causing the block was a user call in the public sdk this is identified by “blocked by=user” so a user level write transaction is blocking some internal workload this is not necessarily a problem, as the internal system will catch up eventually originators https //docs ditto live/ios/common/troubleshooting#originators the three values you’ll see for originator and blocked by are originator / blocked by description “user" any user level api which modifies data “internal" an internal job (presence data, db maintenance, etc ) “replication" updating the store with data received via replication causes of blocked transactions https //docs ditto live/ios/common/troubleshooting#causes of blocked transactions this section presents a few examples blocked transaction scenarios, how they would appear in logs, and what they might mean user blocks user https //docs ditto live/ios/common/troubleshooting#user blocks user an application might block its own write transactions by performing multiple writes at the same time in different places if one is slow (perhaps it does too much work, or perhaps it reaches out to external apis, etc ) then the other write transactions will block until it finishes swift // thread/queue 1 (starts first) { // somewhere in the app, a long running write transaction exists ditto store\["people"] findbyid(docid) update { mutabledoc in // most update tasks are quick, but a developer might // doing something slow within the update block let apidata = getdatafromaslowexternalapicall() // < !!!! mutabledoc?\["age"] = apidata age mutabledoc?\["ownedcars"] set(dittocounter()) mutabledoc?\["ownedcars"] counter? increment(by apidata count) } } // thread/queue 2 (starts second) { // somewhere else in the app, concurrently (e g background thread or queue) // another write transaction tries to update a document // // this will block until the "people" update block above completes let docid = try! ditto store\["settings"] upsert(\[ " id" "abc123", "preference" 31, ]) }// thread/queue 1 (starts first) { // somewhere in the app, a long running write transaction exists ditto store\["people"] findbyid(docid) update { mutabledoc in // most update tasks are quick, but a developer might // doing something slow within the update block let apidata = getdatafromaslowexternalapicall() // < !!!! mutabledoc?\["age"] = apidata age mutabledoc?\["ownedcars"] set(dittocounter()) mutabledoc?\["ownedcars"] counter? increment(by apidata count) } } // thread/queue 2 (starts second) { // somewhere else in the app, concurrently (e g background thread or queue) // another write transaction tries to update a document // // this will block until the "people" update block above completes let docid = try! ditto store\["settings"] upsert(\[ " id" "abc123", "preference" 31, ]) }// thread/queue 1 (starts first) { // somewhere in the app, a long running write transaction exists ditto store\["people"] findbyid(docid) update { mutabledoc in // most update tasks are quick, but a developer might // doing something slow within the update block let apidata = getdatafromaslowexternalapicall() // < !!!! mutabledoc?\["age"] = apidata age mutabledoc?\["ownedcars"] set(dittocounter()) mutabledoc?\["ownedcars"] counter? increment(by apidata count) } } // thread/queue 2 (starts second) { // somewhere else in the app, concurrently (e g background thread or queue) // another write transaction tries to update a document // // this will block until the "people" update block above completes let docid = try! ditto store\["settings"] upsert(\[ " id" "abc123", "preference" 31, ]) }// thread/queue 1 (starts first) { // somewhere in the app, a long running write transaction exists ditto store\["people"] findbyid(docid) update { mutabledoc in // most update tasks are quick, but a developer might // doing something slow within the update block let apidata = getdatafromaslowexternalapicall() // < !!!! mutabledoc?\["age"] = apidata age mutabledoc?\["ownedcars"] set(dittocounter()) mutabledoc?\["ownedcars"] counter? increment(by apidata count) } } // thread/queue 2 (starts second) { // somewhere else in the app, concurrently (e g background thread or queue) // another write transaction tries to update a document // // this will block until the "people" update block above completes let docid = try! ditto store\["settings"] upsert(\[ " id" "abc123", "preference" 31, ]) } in logs, this scenario will look like the following log level waiting for write transaction (elapsed xxs), originator=user blocked by=user note that “user” is blocking “user” this could be temporary, but it could also be a deadlock which is much worse see below user deadlocks user swift // start a write transaction ditto store\["people"] findbyid(docid) update { mutabledoc in // start a write transaction within a write transacton // !! deadlocks !! let docid = try! ditto store\["settings"] upsert(\[ " id" "abc123", "preference" 31, ]) // }// start a write transaction ditto store\["people"] findbyid(docid) update { mutabledoc in // start a write transaction within a write transacton // !! deadlocks !! let docid = try! ditto store\["settings"] upsert(\[ " id" "abc123", "preference" 31, ]) // }// start a write transaction ditto store\["people"] findbyid(docid) update { mutabledoc in // start a write transaction within a write transacton // !! deadlocks !! let docid = try! ditto store\["settings"] upsert(\[ " id" "abc123", "preference" 31, ]) // }// start a write transaction ditto store\["people"] findbyid(docid) update { mutabledoc in // start a write transaction within a write transacton // !! deadlocks !! let docid = try! ditto store\["settings"] upsert(\[ " id" "abc123", "preference" 31, ]) // } you cannot start a write transaction within a write transaction the result will be a deadlock which never resolves itself this might manifest itself in the logs as log level waiting for write transaction (elapsed xxs), originator=user blocked by=user note that user blocking user doesn’t necessarily mean a deadlock it might merely be a long running write transaction and this situation might be expected depending on the task however, if the transaction never unblocks and the log messages at error level continue forever you have a strong indication that there’s a deadlock and should investigate the application code replication blocks user https //docs ditto live/ios/common/troubleshooting#replication blocks user swift // somewhere in the app, a simple enough user write transaction is happening // there's no other user write transaction happening concurrently in the // app, yet the update seems blocked or slow and ui might seems slugglish // or unresponsive let docid = try! ditto store\["settings"] upsert(\[ " id" "abc123", "preference" 31, ])// somewhere in the app, a simple enough user write transaction is happening // there's no other user write transaction happening concurrently in the // app, yet the update seems blocked or slow and ui might seems slugglish // or unresponsive let docid = try! ditto store\["settings"] upsert(\[ " id" "abc123", "preference" 31, ])// somewhere in the app, a simple enough user write transaction is happening // there's no other user write transaction happening concurrently in the // app, yet the update seems blocked or slow and ui might seems slugglish // or unresponsive let docid = try! ditto store\["settings"] upsert(\[ " id" "abc123", "preference" 31, ])// somewhere in the app, a simple enough user write transaction is happening // there's no other user write transaction happening concurrently in the // app, yet the update seems blocked or slow and ui might seems slugglish // or unresponsive let docid = try! ditto store\["settings"] upsert(\[ " id" "abc123", "preference" 31, ]) from the application code, it might not be obvious what the problem is by looking in the logs, you might get a hint for instance log level waiting for write transaction (elapsed xxs), originator=user blocked by=replication here we can see that replication is blocking our user’s write transaction this might happen if we’ve just received a large amount of sync data from another peer most commonly, happens during initial sync (either with the big peer, or joining a mesh for the first time, or re joining a mesh after an extended period away) this scenario is something to be aware of, but will resolve itself automatically once the sync is complete the user transaction will eventually unblock and continue when replication has finished updating the store replication blocks replication https //docs ditto live/ios/common/troubleshooting#replication blocks replication if you see the following in logs, it’s an indication that one replication session with a remote peer is blocking another log level waiting for write transaction (elapsed xxs), originator=replication blocked by=replication this can happen when the device has received large amounts of data from multiple peers simultaneously and must update the database with the changes it received from each ditto can only update the database with sync data from remote peers one at a time, so the other updates must wait their turn this scenario is most likely to happen during a large initial sync either with the big peer at the same time as with nearby devices, or just with multiple nearby devices when joining a mesh for the first time (or re joining after an extended period away) this scenario is something to be aware of, but will resolve itself automatically once the sync is complete you can drastically reduce the chance of this type of blocking transaction by making sure each device is only syncing the data it really needs user or replication blocks internal https //docs ditto live/ios/common/troubleshooting#user or replication blocks internal a long running write transaction might block an internal job this scenario will be the least obvious to spot and it might not be obvious that it’s happening at all it’s also the least likely to cause actual problems https //docs ditto live/ios/common/troubleshooting#user deadlocks user swift // somewhere in the app, a long running write transaction exists ditto store\["people"] findbyid(docid) update { mutabledoc in // most update tasks are quick, but you might // doing something slow within the update block let apidata = getdatafromaslowexternalapicall() // < !!!! mutabledoc?\["age"] = apidata age mutabledoc?\["ownedcars"] set(dittocounter()) mutabledoc?\["ownedcars"] counter? increment(by apidata count) }// somewhere in the app, a long running write transaction exists ditto store\["people"] findbyid(docid) update { mutabledoc in // most update tasks are quick, but you might // doing something slow within the update block let apidata = getdatafromaslowexternalapicall() // < !!!! mutabledoc?\["age"] = apidata age mutabledoc?\["ownedcars"] set(dittocounter()) mutabledoc?\["ownedcars"] counter? increment(by apidata count) }// somewhere in the app, a long running write transaction exists ditto store\["people"] findbyid(docid) update { mutabledoc in // most update tasks are quick, but you might // doing something slow within the update block let apidata = getdatafromaslowexternalapicall() // < !!!! mutabledoc?\["age"] = apidata age mutabledoc?\["ownedcars"] set(dittocounter()) mutabledoc?\["ownedcars"] counter? increment(by apidata count) }// somewhere in the app, a long running write transaction exists ditto store\["people"] findbyid(docid) update { mutabledoc in // most update tasks are quick, but you might // doing something slow within the update block let apidata = getdatafromaslowexternalapicall() // < !!!! mutabledoc?\["age"] = apidata age mutabledoc?\["ownedcars"] set(dittocounter()) mutabledoc?\["ownedcars"] counter? increment(by apidata count) } in logs, you might see the following log level waiting for write transaction (elapsed xxs), originator=internal blocked by=user or log level waiting for write transaction (elapsed xxs), originator=internal blocked by=replication that is, something is blocking internal if you observe slow and unreliable ditto bus connections, an inaccurate presence viewer, or slow and unreliable multi hop data replication, a write transaction may be preventing the internal mesh presence component to persist data in regular 30 second intervals once the blocking write transaction is complete, the observed issue should resolve itself automatically still stuck? if you continue to have problems, please contact us via docid\ yxvys6okqr6 oxsb3jxvc