Skip to content

Commit d9394b3

Browse files
feat(dao): deduplicate concurrent in-flight select() requests in TTLSelectCachingDAO
1 parent b9947b5 commit d9394b3

File tree

1 file changed

+25
-27
lines changed

1 file changed

+25
-27
lines changed

src/foam/dao/TTLSelectCachingDAO.js

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -83,36 +83,34 @@ foam.CLASS({
8383
var self = this;
8484
var key = [sink, skip, limit, order, predicate].toString();
8585

86-
if ( this.cache[key] ) {
87-
// console.log('************************ TTL CACHED:', key);
88-
if ( this.cache[key].clone ) {
89-
/// Need to clone to get expressions to re-evaluate
90-
return Promise.resolve(this.cache[key].clone());
91-
} else {
92-
return Promise.resolve(this.cache[key]);
93-
}
94-
}
86+
var entry = this.cache[key];
9587

96-
return new Promise(function (resolve, reject) {
97-
self.delegate.select_(x, sink, skip, limit, order, predicate).then(s => {
98-
// Clone before storing to prevent cache corruption from mutations.
99-
// Since objects are passed by reference in JavaScript, storing 's' directly
100-
// would cache the same object returned to the caller. Any mutations to the
101-
// returned sink (e.g., modifying array contents, changing properties) would
102-
// corrupt the cached copy, causing incorrect results on subsequent cache hits.
103-
var sinkToCache = s.clone ? s.clone() : s;
104-
self.cache[key] = sinkToCache;
105-
// console.log('************************ TTL CACHING:', key);
106-
// TODO: check if cache is > maxCacheSize and remove oldest entry if it is
107-
self.purgeCache();
108-
resolve(s);
109-
},
110-
e => {
111-
// console.log('************************ TTL ERROR:', e);
112-
self.cache = {};
113-
reject(e);
88+
if ( entry ) {
89+
// In-flight promise — concurrent callers share one request
90+
if ( entry instanceof Promise ) return entry.then(function(s) {
91+
return s.clone ? s.clone() : s;
11492
});
93+
94+
// Resolved result — clone to prevent cache corruption
95+
if ( entry.clone ) return Promise.resolve(entry.clone());
96+
return Promise.resolve(entry);
97+
}
98+
99+
// Store the promise immediately so concurrent calls deduplicate
100+
var promise = self.delegate.select_(x, sink, skip, limit, order, predicate).then(function(s) {
101+
// Replace promise with resolved result for future cache hits
102+
var sinkToCache = s.clone ? s.clone() : s;
103+
self.cache[key] = sinkToCache;
104+
self.purgeCache();
105+
return s;
106+
}, function(e) {
107+
delete self.cache[key];
108+
throw e;
115109
});
110+
111+
this.cache[key] = promise;
112+
113+
return promise;
116114
},
117115

118116
/** Removes are sent to the cache and to the source, ensuring both

0 commit comments

Comments
 (0)