Skip to content

Strong locking in ConcurrentReferenceHashMap#computeIfAbsent may cause context initialisation deadlock #35944

@kusalk

Description

@kusalk

Overview

The change to ConcurrentReferenceHashMap in 12dd758 results in the mapping function now being computed whilst the lock is held. If the mapping function involves access to the same map, that one thread can acquire locks to multiple map segments. If two threads try and acquire locks already held by one another, a deadlock occurs. Below is a test case which illustrates the issue (passes on 6.2.12, fails on 6.2.13). For simplicity, I've used the same two keys in the example below, but in reality, they only need to be keys that hash to the same segment.

This deadlock doesn't present itself as long as the mapping function doesn't involve access to the same map. However, there is such a call in the Spring code during context initialization in: org.springframework.core.annotation.AnnotationTypeMappings$Cache.get(AnnotationTypeMappings.java:273). Thus, complex Spring contexts may occasionally fail to initialise if map access timings and key hashes are unfavorable. Please also see sample thread_dump.txt.

@Test
void deadlock() throws Exception {
    var map = new org.springframework.util.ConcurrentReferenceHashMap<String, String>();
    var deadLockSync = new CountDownLatch(2);
    ExecutorService executor = Executors.newFixedThreadPool(2);

    // Thread 1: key1 -> key2
    executor.submit(() -> {
        try {
            map.computeIfAbsent("key1", k -> {
                deadLockSync.countDown();
                return map.computeIfAbsent("key2", k2 -> "value2");
            });
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    });

    // Thread 2: key2 -> key1
    executor.submit(() -> {
        try {
            map.computeIfAbsent("key2", k -> {
                deadLockSync.countDown();
                return map.computeIfAbsent("key1", k2 -> "value1");
            });
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    });

    executor.shutdown();
    boolean completed = executor.awaitTermination(5, TimeUnit.SECONDS);
    if (!completed) fail("AB-BA Deadlock detected");
}

Related Issues

Metadata

Metadata

Assignees

Labels

in: coreIssues in core modules (aop, beans, core, context, expression)status: backportedAn issue that has been backported to maintenance branchestype: regressionA bug that is also a regression

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions