Skip to content

Commit 3db34ec

Browse files
Merge pull request #2758 from zspitzer/LDEV-1109-ram-drive
LDEV-1109 pin RAM resource directories with HardRef to survive GC
2 parents 345a8fa + 1a4e5d5 commit 3db34ec

File tree

3 files changed

+115
-15
lines changed

3 files changed

+115
-15
lines changed

core/src/main/java/lucee/commons/io/res/type/cache/CacheResourceProvider.java

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,9 @@
1919
package lucee.commons.io.res.type.cache;
2020

2121
import java.io.IOException;
22-
import java.util.HashSet;
2322
import java.util.Iterator;
2423
import java.util.List;
2524
import java.util.Map;
26-
import java.util.Set;
2725

2826
import lucee.commons.io.cache.Cache;
2927
import lucee.commons.io.cache.CacheEntry;
@@ -57,8 +55,6 @@ public final class CacheResourceProvider implements ResourceProviderPro {
5755

5856
private Cache defaultCache;
5957

60-
private Set<Integer> inits = new HashSet<Integer>();
61-
6258
// private Config config;
6359

6460
/**
@@ -152,7 +148,14 @@ String[] getChildNames(String path) throws IOException {
152148
*/
153149
CacheResourceCore createCore(String path, String name, int type) throws IOException {
154150
CacheResourceCore value = new CacheResourceCore(type, path, name);
155-
getCache().put(toKey(path, name), value, null, null);
151+
Cache cache = getCache();
152+
String key = toKey(path, name);
153+
if (type == CacheResourceCore.TYPE_DIRECTORY && cache instanceof RamCache) {
154+
((RamCache) cache).putPinned(key, value, null, null);
155+
}
156+
else {
157+
cache.put(key, value, null, null);
158+
}
156159
return value;
157160
}
158161

@@ -215,18 +218,21 @@ public Cache getCache() {
215218
}
216219
c = defaultCache;
217220
}
218-
if (!inits.contains(c.hashCode())) {
219-
String k = toKey("null", "");
220-
try {
221-
if (!c.contains(k)) {
222-
CacheResourceCore value = new CacheResourceCore(CacheResourceCore.TYPE_DIRECTORY, null, "");
221+
// LDEV-1109: always verify root exists - it may have been lost to GC via soft references
222+
String k = toKey("null", "");
223+
try {
224+
if (!c.contains(k)) {
225+
CacheResourceCore value = new CacheResourceCore(CacheResourceCore.TYPE_DIRECTORY, null, "");
226+
if (c instanceof RamCache) {
227+
((RamCache) c).putPinned(k, value, Constants.LONG_ZERO, Constants.LONG_ZERO);
228+
}
229+
else {
223230
c.put(k, value, Constants.LONG_ZERO, Constants.LONG_ZERO);
224231
}
225232
}
226-
catch (IOException e) {
227-
// simply ignore
228-
}
229-
inits.add(c.hashCode());
233+
}
234+
catch (IOException e) {
235+
// simply ignore
230236
}
231237
return c;
232238
}

core/src/main/java/lucee/runtime/cache/ram/RamCache.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,12 +197,23 @@ public List<String> keys() {
197197

198198
@Override
199199
public void put(String key, Object value, Long idleTime, Long until) {
200+
put(key, value, idleTime, until, false);
201+
}
202+
203+
/**
204+
* Store an entry that is pinned with a hard reference, preventing it from being garbage collected.
205+
* Use this for structural entries (like directory metadata) that must survive memory pressure.
206+
*/
207+
public void putPinned(String key, Object value, Long idleTime, Long until) {
208+
put(key, value, idleTime, until, true);
209+
}
200210

211+
private void put(String key, Object value, Long idleTime, Long until, boolean pinned) {
201212
Ref<RamCacheEntry> tmp = entries.get(key);
202213
RamCacheEntry entry = tmp == null ? null : tmp.get();
203214
if (entry == null) {
204215
RamCacheEntry e = new RamCacheEntry(key, decouple(value), idleTime == null ? this.idleTime : idleTime.longValue(), until == null ? this.until : until.longValue());
205-
entries.put(key, outOfMemory ? new HardRef<RamCacheEntry>(e) : new SoftRef<RamCacheEntry>(e));
216+
entries.put(key, (pinned || outOfMemory) ? new HardRef<RamCacheEntry>(e) : new SoftRef<RamCacheEntry>(e));
206217
}
207218
else entry.update(value);
208219
}

test/tickets/LDEV1109.cfc

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
component extends="org.lucee.cfml.test.LuceeTestCase" labels="ram" {
2+
3+
function afterAll() {
4+
if ( directoryExists( "ram://LDEV1109" ) ) {
5+
directoryDelete( "ram://LDEV1109", true );
6+
}
7+
}
8+
9+
function run( testResults, testBox ) {
10+
describe( "LDEV-1109 RAM drive should survive cache eviction", function() {
11+
12+
it( title="ram:// root should exist after cache is cleared", body=function() {
13+
// verify root exists before clear
14+
expect( directoryExists( "ram://" ) ).toBeTrue();
15+
16+
// clear the cache, simulating GC reclaiming all references
17+
var ramCache = _getRamCache();
18+
ramCache.clear();
19+
20+
// root should be auto-recreated on next access
21+
expect( directoryExists( "ram://" ) ).toBeTrue();
22+
});
23+
24+
it( title="ram:// directories should be stored with HardRef not SoftRef", body=function() {
25+
var dir = "ram://LDEV1109";
26+
// cleanup from previous run
27+
if ( directoryExists( dir ) ) directoryDelete( dir, true );
28+
29+
// create directory structure with a file
30+
directoryCreate( dir );
31+
directoryCreate( dir & "/subdir" );
32+
fileWrite( dir & "/subdir/file.txt", "data" );
33+
34+
// use reflection to access the private entries map in RamCache
35+
var ramCache = _getRamCache();
36+
var entriesField = ramCache.getClass().getDeclaredField( "entries" );
37+
entriesField.setAccessible( true );
38+
var entries = entriesField.get( ramCache );
39+
40+
var HardRef = createObject( "java", "lucee.runtime.cache.ram.ref.HardRef" );
41+
var dirCount = 0;
42+
var fileCount = 0;
43+
44+
var iter = entries.values().iterator();
45+
while ( iter.hasNext() ) {
46+
var ref = iter.next();
47+
var rce = ref.get();
48+
if ( isNull( rce ) ) continue;
49+
var val = rce.getValue();
50+
if ( !isInstanceOf( val, "lucee.commons.io.res.type.cache.CacheResourceCore" ) ) continue;
51+
52+
if ( val.getType() == 1 ) {
53+
// TYPE_DIRECTORY = 1: must be pinned with HardRef
54+
expect( ref ).toBeInstanceOf( "lucee.runtime.cache.ram.ref.HardRef",
55+
"directory entry should use HardRef, not SoftRef" );
56+
dirCount++;
57+
}
58+
else if ( val.getType() == 2 ) {
59+
// TYPE_FILE = 2: should use SoftRef (reclaimable under memory pressure)
60+
expect( ref ).toBeInstanceOf( "lucee.runtime.cache.ram.ref.SoftRef",
61+
"file entry should use SoftRef, not HardRef" );
62+
fileCount++;
63+
}
64+
}
65+
66+
// root + LDEV1109 + subdir = at least 3 directories
67+
expect( dirCount ).toBeGTE( 3, "should have at least 3 directory entries" );
68+
// at least 1 file
69+
expect( fileCount ).toBeGTE( 1, "should have at least 1 file entry" );
70+
});
71+
});
72+
}
73+
74+
private function _getRamCache() {
75+
var providers = getPageContext().getConfig().getResourceProviders();
76+
for ( var p in providers ) {
77+
if ( p.getScheme() == "ram" ) {
78+
return p.getCache();
79+
}
80+
}
81+
throw( message="RAM resource provider not found" );
82+
}
83+
}

0 commit comments

Comments
 (0)