Skip to content

Commit a0628ae

Browse files
authored
[cache] Advise to not store objects in shared cache (#391)
Update docs and log a warning if done so. Refs openhab/openhab-core#4413. Signed-off-by: Florian Hotze <[email protected]>
1 parent e4ec842 commit a0628ae

File tree

9 files changed

+39
-29
lines changed

9 files changed

+39
-29
lines changed

README.md

+15-16
Original file line numberDiff line numberDiff line change
@@ -859,48 +859,47 @@ See [openhab-js : actions.NotificationBuilder](https://openhab.github.io/openhab
859859
860860
### Cache
861861
862-
The cache namespace provides both a private and a shared cache that can be used to set and retrieve objects that will be persisted between subsequent runs of the same or between scripts.
862+
The cache namespace provides both a private and a shared cache that can be used to set and retrieve data that will be persisted between subsequent runs of the same or between scripts.
863863
864864
The private cache can only be accessed by the same script and is cleared when the script is unloaded.
865-
You can use it to e.g. store timers or counters between subsequent runs of that script.
865+
You can use it to store both primitives and objects, e.g. store timers or counters between subsequent runs of that script.
866866
When a script is unloaded and its cache is cleared, all timers (see [`createTimer`](#createtimer)) stored in its private cache are automatically cancelled.
867867
868868
The shared cache is shared across all rules and scripts, it can therefore be accessed from any automation language.
869869
The access to every key is tracked and the key is removed when all scripts that ever accessed that key are unloaded.
870870
If that key stored a timer, the timer will be cancelled.
871+
You can use it to store **only primitives**, as storing objects is not thread-safe and can cause script execution failures.
871872
872873
See [openhab-js : cache](https://openhab.github.io/openhab-js/cache.html) for full API documentation.
873874
874875
- cache : <code>object</code>
875876
- .private
876-
- .get(key, defaultSupplier) ⇒ <code>Object | null</code>
877-
- .put(key, value) ⇒ <code>Previous Object | null</code>
878-
- .remove(key) ⇒ <code>Previous Object | null</code>
877+
- .get(key, defaultSupplier) ⇒ <code>* | null</code>
878+
- .put(key, value) ⇒ <code>Previous * | null</code>
879+
- .remove(key) ⇒ <code>Previous * | null</code>
879880
- .exists(key) ⇒ <code>boolean</code>
880881
- .shared
881-
- .get(key, defaultSupplier) ⇒ <code>Object | null</code>
882-
- .put(key, value) ⇒ <code>Previous Object | null</code>
883-
- .remove(key) ⇒ <code>Previous Object | null</code>
882+
- .get(key, defaultSupplier) ⇒ <code>* | null</code>
883+
- .put(key, value) ⇒ <code>Previous * | null</code>
884+
- .remove(key) ⇒ <code>Previous * | null</code>
884885
- .exists(key) ⇒ <code>boolean</code>
885886
886887
The `defaultSupplier` provided function will return a default value if a specified key is not already associated with a value.
887888
888889
**Example** *(Get a previously set value with a default value (times = 0))*
889890
890891
```js
891-
var counter = cache.private.get('counter', () => ({ 'times': 0 }));
892-
console.log('Count', counter.times++);
892+
var counter = cache.shared.get('counter', () => 0);
893+
console.log('Counter: ' + counter);
893894
```
894895
895-
**Example** *(Get a previously set object)*
896+
**Example** *(Get a previously set value, modify and store it)*
896897
897898
```js
898899
var counter = cache.private.get('counter');
899-
if (counter === null) {
900-
counter = { times: 0 };
901-
cache.private.put('counter', counter);
902-
}
903-
console.log('Count', counter.times++);
900+
counter++;
901+
console.log('Counter: ' + counter);
902+
cache.private.put('counter', counter);
904903
```
905904
906905
### Time

src/cache.js

+17-6
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,18 @@ const { privateCache, sharedCache } = require('@runtime/cache');
1010
* The {@link JSCache} can be used by to share information between subsequent runs of the same script or between scripts (depending on implementation).
1111
*/
1212
class JSCache {
13+
#valueCache;
14+
1315
/**
1416
* @param {*} valueCacheImpl an implementation of the Java {@link https://github.com/openhab/openhab-core/blob/main/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/shared/ValueCache.java ValueCache} interface
1517
* @hideconstructor
1618
*/
1719
constructor (valueCacheImpl) {
18-
this._valueCache = valueCacheImpl;
20+
this.#valueCache = valueCacheImpl;
21+
}
22+
23+
#isSharedCache () {
24+
return this.#valueCache === sharedCache;
1925
}
2026

2127
/**
@@ -27,9 +33,9 @@ class JSCache {
2733
*/
2834
get (key, defaultSupplier) {
2935
if (typeof defaultSupplier === 'function') {
30-
return this._valueCache.get(key, defaultSupplier);
36+
return this.#valueCache.get(key, defaultSupplier);
3137
} else {
32-
return this._valueCache.get(key);
38+
return this.#valueCache.get(key);
3339
}
3440
}
3541

@@ -41,7 +47,10 @@ class JSCache {
4147
* @returns {*|null} the previous value associated with the key, or null if there was no mapping for key
4248
*/
4349
put (key, value) {
44-
return this._valueCache.put(key, value);
50+
if (typeof value === 'object' && this.#isSharedCache()) {
51+
console.warn(`Do not use the shared cache to store the object with key '${key}', as it is not thread-safe and can cause script execution failures. Only store primitives in the shared cache!`);
52+
}
53+
return this.#valueCache.put(key, value);
4554
}
4655

4756
/**
@@ -51,7 +60,7 @@ class JSCache {
5160
* @returns {*|null} the previous value associated with the key or null if there was no mapping for key
5261
*/
5362
remove (key) {
54-
return this._valueCache.remove(key);
63+
return this.#valueCache.remove(key);
5564
}
5665

5766
/**
@@ -61,7 +70,7 @@ class JSCache {
6170
* @returns {boolean} whether the key has a mapping
6271
*/
6372
exists (key) {
64-
return this._valueCache.get(key) !== null;
73+
return this.#valueCache.get(key) !== null;
6574
}
6675
}
6776

@@ -71,6 +80,8 @@ module.exports = {
7180
* The access to every key is tracked and the key is removed when all scripts that ever accessed that key are unloaded.
7281
* If the key that has been auto-removed stored a timer, that timer is cancelled.
7382
*
83+
* Do not use the shared cache to store objects, as it is not thread-safe and can cause script execution failures.
84+
*
7485
* @memberof cache
7586
* @type JSCache
7687
*/

types/cache.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ export class JSCache {
77
* @hideconstructor
88
*/
99
constructor(valueCacheImpl: any);
10-
_valueCache: any;
1110
/**
1211
* Returns the value to which the specified key is mapped.
1312
*
@@ -38,6 +37,7 @@ export class JSCache {
3837
* @returns {boolean} whether the key has a mapping
3938
*/
4039
exists(key: string): boolean;
40+
#private;
4141
}
4242
export declare const shared: JSCache;
4343
declare const _private: JSCache;

types/cache.d.ts.map

+1-1
Original file line numberDiff line numberDiff line change

types/items/metadata/itemchannellink.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export type Item = {
3333
sendCommandIfDifferent(value: any): boolean;
3434
sendIncreaseCommand(value: any): boolean;
3535
sendDecreaseCommand(value: any): boolean;
36-
"__#5@#getToggleState"(): "PAUSE" | "PLAY" | "OPEN" | "CLOSED" | "ON" | "OFF";
36+
"__#6@#getToggleState"(): "PAUSE" | "PLAY" | "OPEN" | "CLOSED" | "ON" | "OFF";
3737
sendToggleCommand(): void;
3838
postToggleUpdate(): void;
3939
postUpdate(value: any): void;

types/items/metadata/metadata.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export type Item = {
2424
sendCommandIfDifferent(value: any): boolean;
2525
sendIncreaseCommand(value: any): boolean;
2626
sendDecreaseCommand(value: any): boolean;
27-
"__#5@#getToggleState"(): "PAUSE" | "PLAY" | "OPEN" | "CLOSED" | "ON" | "OFF";
27+
"__#6@#getToggleState"(): "PAUSE" | "PLAY" | "OPEN" | "CLOSED" | "ON" | "OFF";
2828
sendToggleCommand(): void;
2929
postToggleUpdate(): void;
3030
postUpdate(value: any): void;

types/quantity.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export type Item = {
3737
sendCommandIfDifferent(value: any): boolean;
3838
sendIncreaseCommand(value: any): boolean;
3939
sendDecreaseCommand(value: any): boolean;
40-
"__#5@#getToggleState"(): "PAUSE" | "PLAY" | "OPEN" | "CLOSED" | "ON" | "OFF";
40+
"__#6@#getToggleState"(): "PAUSE" | "PLAY" | "OPEN" | "CLOSED" | "ON" | "OFF";
4141
sendToggleCommand(): void;
4242
postToggleUpdate(): void;
4343
postUpdate(value: any): void;

types/rules/operation-builder.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export type Item = {
3333
sendCommandIfDifferent(value: any): boolean;
3434
sendIncreaseCommand(value: any): boolean;
3535
sendDecreaseCommand(value: any): boolean;
36-
"__#5@#getToggleState"(): "PAUSE" | "PLAY" | "OPEN" | "CLOSED" | "ON" | "OFF";
36+
"__#6@#getToggleState"(): "PAUSE" | "PLAY" | "OPEN" | "CLOSED" | "ON" | "OFF";
3737
sendToggleCommand(): void;
3838
postToggleUpdate(): void;
3939
postUpdate(value: any): void;

types/triggers.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export type Item = {
3333
sendCommandIfDifferent(value: any): boolean;
3434
sendIncreaseCommand(value: any): boolean;
3535
sendDecreaseCommand(value: any): boolean;
36-
"__#5@#getToggleState"(): "PAUSE" | "PLAY" | "OPEN" | "CLOSED" | "ON" | "OFF";
36+
"__#6@#getToggleState"(): "PAUSE" | "PLAY" | "OPEN" | "CLOSED" | "ON" | "OFF";
3737
sendToggleCommand(): void;
3838
postToggleUpdate(): void;
3939
postUpdate(value: any): void;

0 commit comments

Comments
 (0)