Skip to content

Commit f637b98

Browse files
authored
Merge pull request #1188 from omercelikceng/virtualThread
Change "synchronized" to reentrant lock for virtual-threads
2 parents 343b376 + 5240ef3 commit f637b98

File tree

6 files changed

+113
-39
lines changed

6 files changed

+113
-39
lines changed

spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/main/java/org/springframework/cloud/function/adapter/azure/web/AzureWebProxyInvoker.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023-2023 the original author or authors.
2+
* Copyright 2023-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
2020
import java.nio.charset.StandardCharsets;
2121
import java.util.Map.Entry;
2222
import java.util.Optional;
23+
import java.util.concurrent.locks.ReentrantLock;
2324

2425
import com.microsoft.azure.functions.ExecutionContext;
2526
import com.microsoft.azure.functions.HttpMethod;
@@ -48,6 +49,7 @@
4849
*
4950
* @author Christian Tzolov
5051
* @author Oleg Zhurakousky
52+
* @author Omer Celik
5153
*
5254
*/
5355
public class AzureWebProxyInvoker implements FunctionInstanceInjector {
@@ -62,6 +64,8 @@ public class AzureWebProxyInvoker implements FunctionInstanceInjector {
6264

6365
private ServletContext servletContext;
6466

67+
private static final ReentrantLock globalLock = new ReentrantLock();
68+
6569
@SuppressWarnings("unchecked")
6670
@Override
6771
public <T> T getInstance(Class<T> functionClass) throws Exception {
@@ -72,13 +76,20 @@ public <T> T getInstance(Class<T> functionClass) throws Exception {
7276
/**
7377
* Because the getInstance is called by Azure Java Function on every function request we need to cache the Spring
7478
* context initialization on the first function call.
79+
* Double-Checked Locking Optimization was used to avoid unnecessary locking overhead.
7580
* @throws ServletException error.
7681
*/
7782
private void initialize() throws ServletException {
78-
synchronized (AzureWebProxyInvoker.class.getName()) {
79-
if (mvc == null) {
80-
Class<?> startClass = FunctionClassUtils.getStartClass();
81-
this.mvc = ServerlessMVC.INSTANCE(startClass);
83+
if (mvc == null) {
84+
try {
85+
globalLock.lock();
86+
if (mvc == null) {
87+
Class<?> startClass = FunctionClassUtils.getStartClass();
88+
this.mvc = ServerlessMVC.INSTANCE(startClass);
89+
}
90+
}
91+
finally {
92+
globalLock.unlock();
8293
}
8394
}
8495
}

spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/AzureFunctionInstanceInjector.java

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2021-2022 the original author or authors.
2+
* Copyright 2021-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
1717
package org.springframework.cloud.function.adapter.azure;
1818

1919
import java.util.Map;
20+
import java.util.concurrent.locks.ReentrantLock;
2021

2122
import com.microsoft.azure.functions.spi.inject.FunctionInstanceInjector;
2223
import org.apache.commons.logging.Log;
@@ -37,6 +38,7 @@
3738
* hook. The Azure Java Worker delegates scans the classpath for service definition and delegates the function class
3839
* creation to this instance factory.
3940
* @author Christian Tzolov
41+
* @author Omer Celik
4042
* @since 3.2.9
4143
*/
4244
public class AzureFunctionInstanceInjector implements FunctionInstanceInjector {
@@ -45,6 +47,8 @@ public class AzureFunctionInstanceInjector implements FunctionInstanceInjector {
4547

4648
private static ConfigurableApplicationContext APPLICATION_CONTEXT;
4749

50+
private static final ReentrantLock globalLock = new ReentrantLock();
51+
4852
/**
4953
* This method is called by the Azure Java Worker on every function invocation. The Worker sends in the classes
5054
* annotated with @FunctionName annotations and allows the Spring framework to initialize the function instance as a
@@ -83,13 +87,20 @@ public <T> T getInstance(Class<T> functionClass) throws Exception {
8387

8488
/**
8589
* Create a static Application Context instance shared between multiple function invocations.
90+
* Double-Checked Locking Optimization was used to avoid unnecessary locking overhead.
8691
*/
8792
private static void initialize() {
88-
synchronized (AzureFunctionInstanceInjector.class.getName()) {
89-
if (APPLICATION_CONTEXT == null) {
90-
Class<?> springConfigurationClass = FunctionClassUtils.getStartClass();
91-
logger.info("Initializing: " + springConfigurationClass);
92-
APPLICATION_CONTEXT = springApplication(springConfigurationClass).run();
93+
if (APPLICATION_CONTEXT == null) {
94+
try {
95+
globalLock.lock();
96+
if (APPLICATION_CONTEXT == null) {
97+
Class<?> springConfigurationClass = FunctionClassUtils.getStartClass();
98+
logger.info("Initializing: " + springConfigurationClass);
99+
APPLICATION_CONTEXT = springApplication(springConfigurationClass).run();
100+
}
101+
}
102+
finally {
103+
globalLock.unlock();
93104
}
94105
}
95106
}

spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/FunctionInvoker.java

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2021-2022 the original author or authors.
2+
* Copyright 2021-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@
2525
import java.util.Map;
2626
import java.util.Map.Entry;
2727
import java.util.Optional;
28+
import java.util.concurrent.locks.ReentrantLock;
2829

2930
import com.fasterxml.jackson.databind.ObjectMapper;
3031
import com.microsoft.azure.functions.ExecutionContext;
@@ -66,6 +67,7 @@
6667
* @author Oleg Zhurakousky
6768
* @author Chris Bono
6869
* @author Christian Tzolov
70+
* @author Omer Celik
6971
*
7072
* @since 3.2
7173
*
@@ -85,6 +87,8 @@ public class FunctionInvoker<I, O> {
8587

8688
private static JsonMapper OBJECT_MAPPER;
8789

90+
private static final ReentrantLock globalLock = new ReentrantLock();
91+
8892
public FunctionInvoker(Class<?> configurationClass) {
8993
try {
9094
initialize(configurationClass);
@@ -355,30 +359,38 @@ private MessageHeaders getHeaders(HttpRequestMessage<I> event) {
355359
return new MessageHeaders(headers);
356360
}
357361

362+
/**
363+
* Double-Checked Locking Optimization was used to avoid unnecessary locking overhead.
364+
*/
358365
private static void initialize(Class<?> configurationClass) {
359-
synchronized (FunctionInvoker.class.getName()) {
360-
if (FUNCTION_CATALOG == null) {
361-
logger.info("Initializing: " + configurationClass);
362-
SpringApplication builder = springApplication(configurationClass);
363-
APPLICATION_CONTEXT = builder.run();
364-
365-
Map<String, FunctionCatalog> mf = APPLICATION_CONTEXT.getBeansOfType(FunctionCatalog.class);
366-
if (CollectionUtils.isEmpty(mf)) {
367-
OBJECT_MAPPER = new JacksonMapper(new ObjectMapper());
368-
JsonMessageConverter jsonConverter = new JsonMessageConverter(OBJECT_MAPPER);
369-
SmartCompositeMessageConverter messageConverter = new SmartCompositeMessageConverter(
366+
if (FUNCTION_CATALOG == null) {
367+
try {
368+
globalLock.lock();
369+
if (FUNCTION_CATALOG == null) {
370+
logger.info("Initializing: " + configurationClass);
371+
SpringApplication builder = springApplication(configurationClass);
372+
APPLICATION_CONTEXT = builder.run();
373+
374+
Map<String, FunctionCatalog> mf = APPLICATION_CONTEXT.getBeansOfType(FunctionCatalog.class);
375+
if (CollectionUtils.isEmpty(mf)) {
376+
OBJECT_MAPPER = new JacksonMapper(new ObjectMapper());
377+
JsonMessageConverter jsonConverter = new JsonMessageConverter(OBJECT_MAPPER);
378+
SmartCompositeMessageConverter messageConverter = new SmartCompositeMessageConverter(
370379
Collections.singletonList(jsonConverter));
371-
FUNCTION_CATALOG = new SimpleFunctionRegistry(
380+
FUNCTION_CATALOG = new SimpleFunctionRegistry(
372381
APPLICATION_CONTEXT.getBeanFactory().getConversionService(),
373382
messageConverter, OBJECT_MAPPER);
374-
}
375-
else {
376-
OBJECT_MAPPER = APPLICATION_CONTEXT.getBean(JsonMapper.class);
377-
FUNCTION_CATALOG = mf.values().iterator().next();
383+
}
384+
else {
385+
OBJECT_MAPPER = APPLICATION_CONTEXT.getBean(JsonMapper.class);
386+
FUNCTION_CATALOG = mf.values().iterator().next();
387+
}
378388
}
379389
}
390+
finally {
391+
globalLock.unlock();
392+
}
380393
}
381-
382394
}
383395

384396
private static SpringApplication springApplication(Class<?> configurationClass) {

spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ServerlessAsyncContext.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023-2023 the original author or authors.
2+
* Copyright 2023-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
1919
import java.io.IOException;
2020
import java.util.ArrayList;
2121
import java.util.List;
22+
import java.util.concurrent.locks.ReentrantLock;
2223

2324
import jakarta.servlet.AsyncContext;
2425
import jakarta.servlet.AsyncEvent;
@@ -39,6 +40,7 @@
3940
* Implementation of Async context for {@link ServerlessMVC}.
4041
*
4142
* @author Oleg Zhurakousky
43+
* @author Omer Celik
4244
*/
4345
public class ServerlessAsyncContext implements AsyncContext {
4446
private final HttpServletRequest request;
@@ -55,6 +57,8 @@ public class ServerlessAsyncContext implements AsyncContext {
5557

5658
private final List<Runnable> dispatchHandlers = new ArrayList<>();
5759

60+
private final ReentrantLock globalLock = new ReentrantLock();
61+
5862

5963
public ServerlessAsyncContext(ServletRequest request, @Nullable ServletResponse response) {
6064
this.request = (HttpServletRequest) request;
@@ -64,14 +68,18 @@ public ServerlessAsyncContext(ServletRequest request, @Nullable ServletResponse
6468

6569
public void addDispatchHandler(Runnable handler) {
6670
Assert.notNull(handler, "Dispatch handler must not be null");
67-
synchronized (this) {
71+
try {
72+
this.globalLock.lock();
6873
if (this.dispatchedPath == null) {
6974
this.dispatchHandlers.add(handler);
7075
}
7176
else {
7277
handler.run();
7378
}
7479
}
80+
finally {
81+
this.globalLock.unlock();
82+
}
7583
}
7684

7785
@Override
@@ -102,10 +110,14 @@ public void dispatch(String path) {
102110

103111
@Override
104112
public void dispatch(@Nullable ServletContext context, String path) {
105-
synchronized (this) {
113+
try {
114+
this.globalLock.lock();
106115
this.dispatchedPath = path;
107116
this.dispatchHandlers.forEach(Runnable::run);
108117
}
118+
finally {
119+
this.globalLock.unlock();
120+
}
109121
}
110122

111123
@Nullable

spring-cloud-function-context/src/main/java/org/springframework/cloud/function/utils/JsonMasker.java

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.Map;
2828
import java.util.Set;
2929
import java.util.TreeSet;
30+
import java.util.concurrent.locks.ReentrantLock;
3031

3132
import com.fasterxml.jackson.databind.ObjectMapper;
3233
import org.apache.commons.logging.Log;
@@ -39,6 +40,7 @@
3940

4041
/**
4142
* @author Oleg Zhurakousky
43+
* @author Omer Celik
4244
*/
4345
public final class JsonMasker {
4446

@@ -50,22 +52,41 @@ public final class JsonMasker {
5052

5153
private final Set<String> keysToMask;
5254

55+
private static final ReentrantLock globalLock = new ReentrantLock();
56+
5357
private JsonMasker() {
5458
this.keysToMask = loadKeys();
5559
this.mapper = new JacksonMapper(new ObjectMapper());
5660

5761
}
5862

59-
public synchronized static JsonMasker INSTANCE() {
63+
/**
64+
* Double-Checked Locking Optimization was used to avoid unnecessary locking overhead.
65+
*/
66+
public static JsonMasker INSTANCE() {
6067
if (jsonMasker == null) {
61-
jsonMasker = new JsonMasker();
68+
try {
69+
globalLock.lock();
70+
if (jsonMasker == null) {
71+
jsonMasker = new JsonMasker();
72+
}
73+
}
74+
finally {
75+
globalLock.unlock();
76+
}
6277
}
6378
return jsonMasker;
6479
}
6580

66-
public synchronized static JsonMasker INSTANCE(Set<String> keysToMask) {
67-
INSTANCE().addKeys(keysToMask);
68-
return jsonMasker;
81+
public static JsonMasker INSTANCE(Set<String> keysToMask) {
82+
try {
83+
globalLock.lock();
84+
INSTANCE().addKeys(keysToMask);
85+
return jsonMasker;
86+
}
87+
finally {
88+
globalLock.unlock();
89+
}
6990
}
7091

7192
public String[] getKeysToMask() {

spring-cloud-function-integration/src/main/java/org/springframework/cloud/function/integration/dsl/FunctionLookupHelper.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023-2023 the original author or authors.
2+
* Copyright 2023-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
1717
package org.springframework.cloud.function.integration.dsl;
1818

1919
import java.util.concurrent.atomic.AtomicReference;
20+
import java.util.concurrent.locks.ReentrantLock;
2021
import java.util.function.Consumer;
2122
import java.util.function.Function;
2223
import java.util.function.Supplier;
@@ -28,6 +29,7 @@
2829
* The helper class to lookup functions from the catalog in lazy manner and cache their instances.
2930
*
3031
* @author Artem Bilan
32+
* @author Omer Celik
3133
*
3234
* @since 4.0.3
3335
*/
@@ -72,16 +74,21 @@ private <T> T requireNonNull(Class<?> functionType, String functionDefinition)
7274
*/
7375
private static <T> Supplier<T> memoize(Supplier<? extends T> delegate) {
7476
AtomicReference<T> value = new AtomicReference<>();
77+
ReentrantLock lock = new ReentrantLock();
7578
return () -> {
7679
T val = value.get();
7780
if (val == null) {
78-
synchronized (value) {
81+
try {
82+
lock.lock();
7983
val = value.get();
8084
if (val == null) {
8185
val = delegate.get();
8286
value.set(val);
8387
}
8488
}
89+
finally {
90+
lock.unlock();
91+
}
8592
}
8693
return val;
8794
};

0 commit comments

Comments
 (0)