Skip to content

Commit

Permalink
fixed concurrency issue in saving large graphs of objects
Browse files Browse the repository at this point in the history
it was possible that if object A was already being saved
and object B required A.id, then it would take A.id
before A was saved.

This is now fixed such that all dependent objects strictly
wait for their dependencies to be saved before attempting
to get their ids. This happens even if the object is already
being saved by another operation. (the promise is shared)

On the other hand, we don't wait for all one to many objects
for an object - which don't have a dependency relationship.
These objects are added to a queue of objects that need to
be saved before the top level object can be considered
completely saved.
  • Loading branch information
refractalize committed Mar 11, 2016
1 parent c30c2de commit 0c64d69
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 16 deletions.
56 changes: 40 additions & 16 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,11 @@ var rowBase = function() {
}
}

function saveManyToOne(obj, field) {
function saveManyToOne(obj, field, options) {
var value = foreignField(obj, field);

if (value && !(value instanceof Array)) {
return value.save().then(function () {
return value.save(options).then(function () {
var foreignId =
obj._meta.foreignKeyFor ?
obj._meta.foreignKeyFor(field) :
Expand All @@ -128,27 +128,27 @@ var rowBase = function() {
}
}

function saveManyToOnes(obj) {
function saveManyToOnes(obj, options) {
return Promise.all(foreignFieldsForObject(obj).map(function (field) {
return saveManyToOne(obj, field);
return saveManyToOne(obj, field, options);
}));
}

function saveOneToMany(obj, field) {
function saveOneToMany(obj, field, options) {
var items = foreignField(obj, field);

if (items instanceof Array) {
return Promise.all(items.map(function (item) {
return item.save();
return item.save(options);
}));
} else {
return Promise.resolve();
}
}

function saveOneToManys(obj) {
function saveOneToManys(obj, options) {
return Promise.all(foreignFieldsForObject(obj).map(function (field) {
return saveOneToMany(obj, field);
return saveOneToMany(obj, field, options);
}));
}

Expand All @@ -168,27 +168,51 @@ var rowBase = function() {
var self = this;
var force = options && options.hasOwnProperty('force')? options.force: false;

if (!self._saving) {
self.setSaving(true);
var waitForOneToManys;
var oneToManyPromises;

return saveManyToOnes(this).then(function () {
if (typeof options == 'object' && options.hasOwnProperty('oneToManyPromises')) {
waitForOneToManys = false;
oneToManyPromises = options.oneToManyPromises;
} else {
waitForOneToManys = true;
oneToManyPromises = [];
}

if (!self._saving) {
self.setSaving(saveManyToOnes(this, {oneToManyPromises: oneToManyPromises}).then(function () {
if (self.changed() || force) {
var writePromise = self.saved() ? update(self) : insert(self);

return writePromise.then(function () {
return saveOneToManys(self);
return {
oneToManys: saveOneToManys(self, {oneToManyPromises: oneToManyPromises})
};
});
} else {
return saveOneToManys(self);
return {
oneToManys: saveOneToManys(self, {oneToManyPromises: oneToManyPromises})
};
}
}).then(function () {
}).then(function (value) {
self.setSaving(false);
return value;
}, function (error) {
self.setSaving(false);
throw error;
}));
}

oneToManyPromises.push(self._saving.then(function (r) {
return r.oneToManys;
}));

if (waitForOneToManys) {
return self._saving.then(function () {
return Promise.all(oneToManyPromises);
});
} else {
return Promise.resolve();
return self._saving;
}
},

Expand All @@ -214,7 +238,7 @@ var rowBase = function() {
setSaving: function(saving) {
if (saving) {
Object.defineProperty(this, "_saving", {
value: true,
value: saving,
configurable: true
});
} else {
Expand Down
21 changes: 21 additions & 0 deletions test/spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,27 @@ function describeDatabase(name, config) {
});
});

it("can save a shared foreign object", function() {
var essert = address({
address: "15, Rue d'Essert"
});

var bob = person({
name: "bob",
address: essert
});

var jane = person({
name: "jane",
address: essert
});

return Promise.all([bob.save(), jane.save()]).then(function() {
expect(bob.address_id).to.equal(essert.id);
expect(jane.address_id).to.equal(essert.id);
});
});

it("can save a many to one relationship with function", function() {
var bobsAddress;
var bob = person({
Expand Down

0 comments on commit 0c64d69

Please sign in to comment.