TransactionInactiveError with subsequent put calls - javascript

I can't figure out if I'm doing something wrong or if I'm just pushing it to hard.
I'm trying to sync ~70000 records from my online db to IndexedDB in combination with EventSource and a Worker.
So I get 2000 records per package and then use the following code to store them in IndexedDB:
eventSource.addEventListener('package', function(e) {
var data = JSON.parse(e.data);
putData(data.type, data.records);
});
function putData(storeName, data) {
var store = db.transaction([storeName], 'readwrite').objectStore(storeName);
return new Promise(function(resolve, reject) {
putRecord(data, store, 0);
store.transaction.oncomplete = resolve;
store.transaction.onerror = reject;
});
}
function putRecord(data, store, recordIndex) {
if(recordIndex < data.length){
var req = store.put(data[recordIndex]);
req.onsuccess = function(e) {
recordIndex += 1;
putRecord(data, store, recordIndex);
};
req.onerror = function() {
self.postMessage(this.error.name);
recordIndex += 1;
putRecord(data, store, recordIndex);
};
}
}
It all works for about ~10000 records. Didn't really test where the limit is though. I suspect that at some point there are too many transactions in parallel which causes a single transaction to be very slow and thus causing trouble because of some timeout. According to the dev tools the 70000 records are around 20MB.
Complete error:
Uncaught TransactionInactiveError: Failed to execute 'put' on
'IDBObjectStore': The transaction has finished.
Any ideas?

I don't see an obvious error in your code, but you can make it much simpler and faster. There's no need to wait for the success of a previous put() to issue a second put() request.
function putData(storeName, data) {
var store = db.transaction([storeName], 'readwrite').objectStore(storeName);
return new Promise(function(resolve, reject) {
for (var i = 0; i < data.length; ++i) {
var req = store.put(data[i]);
req.onerror = function(e) {
self.postMessage(e.target.error.name);
};
}
store.transaction.oncomplete = resolve;
store.transaction.onerror = reject;
});
}
It is possible that the error you are seeing is because the browser has implemented an arbitrary time limit on the transaction. But again, your code looks correct, including the use of Promises (which are tricky with IDB, but so far as I can tell you're doing it correctly!)
If this is still occurring I second the comment to file a bug against the browser(s) with a stand-alone repro. (If it's happening in Chrome I'd be happy to take a look.)

I think this is due the implementation. If you read the specs a transaction must keep a list of all the requests made in the transaction. When the transaction is commited all these changes are persisted otherwise the transaction will be aborted. Specs
Probably is the maximum request list in your case a 1000 request. You can easily test that by trying to insert a 1001 records. So my guess is when the 1000 request is reached, the transaction is set to inactive.
Maybe change your stratigy and only make 1000 request in every transaction and start a new transaction when the other one is completed.

Related

Trying to clear cache in Electron but it doesn't seem to work

Context: Adding a feature to an Electron app to check license data, etc. on start up by sending a query string using XMLHttpRequest to a remote server. In my testing rig, after a fresh launch an initial request will take 3-4 seconds but subsequent requests return immediately. That worries me.
So a caching issue? I haven't done much network stuff but searching around I saw this (How to clear the cache data in Electron(atom shell)?). Alas, it does not seem to make a difference. I have also tried adding a timestamp to the end of the query string to try to force a new request.
Am I doing something obviously wrong? I've upgraded to the latest version of Electron and have seen no difference.
function clearCache() {
var win = remote.getCurrentWindow();
win.webContents.session.clearStorageData(null, () => {
// this NEVER gets called
console.log('session cleared');
});
win.webContents.session.clearCache(function () {
// this DOES get called
console.log('cache cleared', (new Date()).getTime());
});
}
// attempt to force new request
var timestamp = (new Date()).getTime();
obj.timestamp = timestamp;
var url = domain + queryString.stringify(obj);
xhttp.open("GET", url);
xhttp.timeout = 1000 * 30;
xhttp.send();

Avoid XMLHttpRequest chain calling leak

In my code (a monitoring application) I need to periodically call the server with an XMLHttpRequest object in the form of chained calls. Each call takes exactly 15 seconds, which is timed by the server as it delivers several partial results within that period (HTTP 100 Continue). Immediately after finishing the current call, the onreadystatechange event handler of the current XMLHttpRequest object needs to create and launch the next request (with a new instance), so the communication with the server remains almost seamless.
The way it works, each call retains the object context of the caller in the stack, so as this is a page that must remain open for days, the stack keeps growing with no chance for the garbage collector to claim the data. See the following stack trace:
I cannot use timers (setInterval or such) to launch the next request. It should be launched from inside the ending of the previous one. The data from server must arrive as quickly as possible, and unfortunately browsers nowadays throtle timers when a page is not in focus. As I said, this is a monitoring application meant to be always on in the users' secondary monitors (rarely in focus). I also need to deal with HTTP timeouts and other kinds of errors that derail from the 15 second sequence. There should always be one and only one channel open with the server.
My question is whether is any way to avoid keeping the whole context in the stack when creating an XMLHttpRequest object. Even calling the click() method on a DOM object will keep the stack/context alive. Even promises seem to keep the context.
I'm also unable to use websockets, as the server does not support them.
UPDATE:
It's more complex, buy in essence it's like:
var xhttpObjUrl;
var xhttpObj;
onLoad() {
loadXMLDoc(pollURL + "first=1", true);
}
function loadXMLDoc(url, longtout) {
xhttpObjUrl = url;
xhttpObj = new XMLHttpRequest();
xhttpObj.open(method, url, true);
xhttpObj.onprogress = progress;
xhttpObj.onloadend = progress;
xhttpObj.ontimeout = progress;
if (commlog) consolelog("loadXMLDoc(): url == " + dname);
xhttpObj.send("");
}
function progress() {
if (!xhttpObj) return;
var state = xhttpObj.readyState;
var status;
var statusText;
if (state == 4 /* complete */ || state == 3 /* partial content */) {
try {
status = xhttpObj.status;
statusText = xhttpObj.statusText;
if (status == 200) parseServerData();
} catch (err) {
status = 500;
statusText = err;
}
if (state == 4 || status != 200) {
/* SERVER TERMINATES THE CONNECTION AFTER 15 SECONDS */
/* ERROR HANDLING REMOVED */
var obj = xhttpObj;
xhttpObj = undefined;
abortRequest(obj);
obj = false;
RequestEnd();
}
}
}
function RequestEnd(error) {
var now = (new Date).getTime();
var msdiff = now - lastreqstart;
var code = function () { loadXMLDoc(pollURL + 'lastpoint=' + evtprev.toString() + '&lastevent=' + evtcurrent.toString()); return false; };
if (msdiff < 1000) addTimedCheck(1, code); /** IGNORE THIS **/
else code();
}
I've solved my problem using a web worker. The worker would end the XMLHttpRequest each time and send the page a message with the collected data. Then, when the page finishes processing the data, it would send the worker a message to start a new request. Thus my page wouldn't have any unwanted delays between requests, and there's no stack constantly building up. On error I'd terminate the worker and create a new one, just in case.

Asynchronously update IndexedDB in upgrade event

In the onupgradeneeded() event for IndexedDB I am trying to update each record in an object store. In order to update them I need to first preform an async operation but this causes the upgrade transaction to become inactive and I get the error
Failed to execute 'update' on 'IDBCursor': The transaction is not active.
In the follow code I am simulating an async operation with setTimeout()
let openRequest = indexedDB.open('myDb', 1);
openRequest.onupgradeneeded = function (versionEvent) {
let db = versionEvent.target['result'];
let upgradeTransaction = versionEvent.target['transaction'];
if(versionEvent.oldVersion < 1) {
let objStore = db.createObjectStore('sample');
objStore.add('one', '1');
objStore.add('two', '2');
}
if(versionEvent.oldVersion >= 1) {
let getCursor = upgradeTransaction.objectStore('sample').openCursor();
getCursor.onsuccess = (e) => {
let cursor = e.target['result'];
if (cursor) {
setTimeout(() => {
cursor.update(cursor.value + ' updated');
cursor.continue();
})
}
}
}
};
https://plnkr.co/edit/DIIzLduZT1dwOEHAdzSf?p=preview
If you run this plunker it will initialize IndexedDB. Then if you increase the version number to 2 and run it again you will get the error.
How can I update IndexedDB in an upgrade event if my update relies on an async operation?
You need a different approach. Options include:
Making the schema changes immediately, but deferring adding new data for a subsequent transaction.
Fetch the data before trying to perform the upgrade. Since fetching the data is slow this is likely not desirable.
Conditionally fetch the data, only if an upgrade is needed.
There are two approaches for the latter. You could do an open() with no version number, check the version, and then fetch/upgrade if it is lower than desired. Or you can open at the new version and in upgradeneeded abort the upgrade (get the transaction from the request and call abort() on it) then fetch the data and re-attempt the upgrade.

How to get the value of SELECT COUNT(*)?

I've literally been trying all day to make Firefox to obey my will...
I want :
int c = SELECT COUNT(*) FROM ...
I've tried executeAsync({...});, but I believe it's the wrong paradigm, as I want the result immediately. (And mozIStoragePendingStatement results in errors)
var count = 0;
var conn = Services.storage.openDatabase(dbfile); // Will also create the file if it does not exist
let statement = conn.createStatement("SELECT COUNT(*) FROM edges LIMIT 42;");
console.log("columns: " + statement.columnCount); // prints "1";
console.log("col name:" + statement.getColumnName(0)); // is "COUNT(*)"
while (statement.executeStep())
count = statement.row.getResultByIndex(0); // "illegal value"
count = statement.row.getString(0); // "illegal value", too
count = statement.row.COUNT(*); // hahaha. still not working
count = statement.row[0]; // hahaha. "undefinded"
count = statement.row[1]; // hahaha. "undefinded"
}
statement.reset();
It basically works but I dont get the value. What's wrong with all the statements (those within the loop).
Thanks for any hints...
I've tried executeAsync({...});, but I believe it's the wrong paradigm, as I want the result immediately.
You shouldn't want that, the Storage API is asynchronous for a reason. Synchronous access to databases can cause a random delay (e.g. if the hard drive is busy). And since your code executes on the main thread (the same thread that services the user interface) the entire user interface would hang while your code is waiting for the database to respond. The Mozilla devs tried synchronous database access in Firefox 3 and quickly noticed that it degrades user experience - hence the asynchronous API, the database processing happens on a background thread without blocking anything.
You should change your code to work asynchronously. Something like this should do for example:
Components.utils.import("resource://gre/modules/Services.jsm");
var conn = Services.storage.openDatabase(dbfile);
if (conn.schemaVersion < 1)
{
conn.createTable("edges", "s INTEGER, t INTEGER");
conn.schemaVersion = 1;
}
var statement = conn.createStatement("SELECT COUNT(*) FROM edges");
statement.executeAsync({
handleResult: function(resultSet)
{
var row = resultSet.getNextRow();
var count = row.getResultByIndex(0);
processResult(count);
},
handleError: function(error) {},
handleCompletion: function(reason) {}
});
// Close connection once the pending operations are completed
conn.asyncClose();
See also: mozIStorageResultSet, mozIStorageRow.
Try aliasing count(*) as total, then fetch that

Consecutive Ajax requests without jQuery/ JS library

I have an issue, mainly with IE.
I need to be able to handle n queries one after another. But If I simply call my function below in a for loop IE does some strange things (like loading only so many of the calls).
If I use an alert box it proves that the function gets all of the calls, and surprisingly IT WORKS!
My guess is that IE needs more time than other browsers, and the alert box does just that.
Here is my code:
var Ajax = function(all) {
this.xhr = new XMLHTTPREQUEST(); // Function returns xhr object/ activeX
this.uri = function(queries) { // Takes an object and formats query string
var qs = "", i = 0, len = size(queries);
for (value in queries) {
qs += value + "=" + queries[value];
if (++i <= len) { qs += "&"; }
}
return qs;
};
xhr.onreadystatechange = function() { // called when content is ready
if (this.readyState === 4) {
if (this.status === 200) {
all.success(this.responseText, all.params);
}
this.abort();
}
};
this.post = function() { // POST
xhr.open("POST", all.where, true);
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xhr.send(uri(all.queries));
};
this.get = function() { // GET
xhr.open("GET", all.where + "?" + uri(all.queries), true);
xhr.send();
};
if (this instanceof Ajax) {
return this.Ajax;
} else {
return new Ajax(all);
}
};
This function works perfectly for a single request, but how can I get it to work when called so many times within a loop?
I think the problem might be related to the 2 concurrent connections limit that most web browsers implement.
It looks like the latency of your web service to respond is making your AJAX requests overlap, which in turn is exceeding the 2 concurrent connections limit.
You may want to check out these articles regarding this limitation:
The Dreaded 2 Connection Limit
The Two HTTP Connection Limit Issue
Circumventing browser connection limits for fun and profit
This limit is also suggested in the HTTP spec: section 8.14 last paragraph, which is probably the main reason why most browsers impose it.
To work around this problem, you may want to consider the option of relaunching your AJAX request ONLY after a successful response from the previous AJAX call. This will prevent the overlap from happening. Consider the following example:
function autoUpdate () {
var ajaxConnection = new Ext.data.Connection();
ajaxConnection.request({
method: 'GET',
url: '/web-service/',
success: function (response) {
// Add your logic here for a successful AJAX response.
// ...
// ...
// Relaunch the autoUpdate() function in 100ms. (Could be less or more)
setTimeout(autoUpdate, 100);
}
}
}
This example uses ExtJS, but you could very easily use just XMLHttpRequest.
Given that the limit to a single domain is 2 concurrent connections in most browsers, it doesn't confer any speed advantage launching more than 2 concurrent requests. Launch 2 requests, and dequeue and launch another each time one completes.
I'd suggest throttling your requests so you only have a few (4?) outstanding at any given time. You're probably seeing the result of multiple requests being queued and timing out before your code can handle them all. Just a gess though. We have an ajax library that has built-in throttling and queues the requests so we only have 4 outstanding at any one time and don't see any problems. We routinely q lots per page.
Your code looks like it's put together using the constructor pattern. Are you invoking it with the new operator like var foo = new Ajax(...) in your calling code? Or are you just calling it directly like var foo = Ajax(...) ?
If the latter, you're likely overwriting state on your later calls. It looks like it's designed to be called to create an object, on which the get/post methods are called. This could be your problem if you're "calling it within a loop" as you say.

Categories

Resources