Merge pull request #1188 from omercelikceng/virtualThread
Change "synchronized" to reentrant lock for virtual-threads
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2023-2023 the original author or authors.
|
* Copyright 2023-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -20,6 +20,7 @@ import java.nio.charset.Charset;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
import com.microsoft.azure.functions.ExecutionContext;
|
import com.microsoft.azure.functions.ExecutionContext;
|
||||||
import com.microsoft.azure.functions.HttpMethod;
|
import com.microsoft.azure.functions.HttpMethod;
|
||||||
@@ -48,6 +49,7 @@ import org.springframework.util.StringUtils;
|
|||||||
*
|
*
|
||||||
* @author Christian Tzolov
|
* @author Christian Tzolov
|
||||||
* @author Oleg Zhurakousky
|
* @author Oleg Zhurakousky
|
||||||
|
* @author Omer Celik
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class AzureWebProxyInvoker implements FunctionInstanceInjector {
|
public class AzureWebProxyInvoker implements FunctionInstanceInjector {
|
||||||
@@ -62,6 +64,8 @@ public class AzureWebProxyInvoker implements FunctionInstanceInjector {
|
|||||||
|
|
||||||
private ServletContext servletContext;
|
private ServletContext servletContext;
|
||||||
|
|
||||||
|
private static final ReentrantLock globalLock = new ReentrantLock();
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@Override
|
||||||
public <T> T getInstance(Class<T> functionClass) throws Exception {
|
public <T> T getInstance(Class<T> functionClass) throws Exception {
|
||||||
@@ -72,13 +76,20 @@ public class AzureWebProxyInvoker implements FunctionInstanceInjector {
|
|||||||
/**
|
/**
|
||||||
* Because the getInstance is called by Azure Java Function on every function request we need to cache the Spring
|
* Because the getInstance is called by Azure Java Function on every function request we need to cache the Spring
|
||||||
* context initialization on the first function call.
|
* context initialization on the first function call.
|
||||||
|
* Double-Checked Locking Optimization was used to avoid unnecessary locking overhead.
|
||||||
* @throws ServletException error.
|
* @throws ServletException error.
|
||||||
*/
|
*/
|
||||||
private void initialize() throws ServletException {
|
private void initialize() throws ServletException {
|
||||||
synchronized (AzureWebProxyInvoker.class.getName()) {
|
if (mvc == null) {
|
||||||
if (mvc == null) {
|
try {
|
||||||
Class<?> startClass = FunctionClassUtils.getStartClass();
|
globalLock.lock();
|
||||||
this.mvc = ServerlessMVC.INSTANCE(startClass);
|
if (mvc == null) {
|
||||||
|
Class<?> startClass = FunctionClassUtils.getStartClass();
|
||||||
|
this.mvc = ServerlessMVC.INSTANCE(startClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
globalLock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2021-2022 the original author or authors.
|
* Copyright 2021-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
package org.springframework.cloud.function.adapter.azure;
|
package org.springframework.cloud.function.adapter.azure;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
import com.microsoft.azure.functions.spi.inject.FunctionInstanceInjector;
|
import com.microsoft.azure.functions.spi.inject.FunctionInstanceInjector;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
@@ -37,6 +38,7 @@ import org.springframework.util.CollectionUtils;
|
|||||||
* hook. The Azure Java Worker delegates scans the classpath for service definition and delegates the function class
|
* hook. The Azure Java Worker delegates scans the classpath for service definition and delegates the function class
|
||||||
* creation to this instance factory.
|
* creation to this instance factory.
|
||||||
* @author Christian Tzolov
|
* @author Christian Tzolov
|
||||||
|
* @author Omer Celik
|
||||||
* @since 3.2.9
|
* @since 3.2.9
|
||||||
*/
|
*/
|
||||||
public class AzureFunctionInstanceInjector implements FunctionInstanceInjector {
|
public class AzureFunctionInstanceInjector implements FunctionInstanceInjector {
|
||||||
@@ -45,6 +47,8 @@ public class AzureFunctionInstanceInjector implements FunctionInstanceInjector {
|
|||||||
|
|
||||||
private static ConfigurableApplicationContext APPLICATION_CONTEXT;
|
private static ConfigurableApplicationContext APPLICATION_CONTEXT;
|
||||||
|
|
||||||
|
private static final ReentrantLock globalLock = new ReentrantLock();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is called by the Azure Java Worker on every function invocation. The Worker sends in the classes
|
* This method is called by the Azure Java Worker on every function invocation. The Worker sends in the classes
|
||||||
* annotated with @FunctionName annotations and allows the Spring framework to initialize the function instance as a
|
* annotated with @FunctionName annotations and allows the Spring framework to initialize the function instance as a
|
||||||
@@ -83,13 +87,20 @@ public class AzureFunctionInstanceInjector implements FunctionInstanceInjector {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a static Application Context instance shared between multiple function invocations.
|
* Create a static Application Context instance shared between multiple function invocations.
|
||||||
|
* Double-Checked Locking Optimization was used to avoid unnecessary locking overhead.
|
||||||
*/
|
*/
|
||||||
private static void initialize() {
|
private static void initialize() {
|
||||||
synchronized (AzureFunctionInstanceInjector.class.getName()) {
|
if (APPLICATION_CONTEXT == null) {
|
||||||
if (APPLICATION_CONTEXT == null) {
|
try {
|
||||||
Class<?> springConfigurationClass = FunctionClassUtils.getStartClass();
|
globalLock.lock();
|
||||||
logger.info("Initializing: " + springConfigurationClass);
|
if (APPLICATION_CONTEXT == null) {
|
||||||
APPLICATION_CONTEXT = springApplication(springConfigurationClass).run();
|
Class<?> springConfigurationClass = FunctionClassUtils.getStartClass();
|
||||||
|
logger.info("Initializing: " + springConfigurationClass);
|
||||||
|
APPLICATION_CONTEXT = springApplication(springConfigurationClass).run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
globalLock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2021-2022 the original author or authors.
|
* Copyright 2021-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -25,6 +25,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.microsoft.azure.functions.ExecutionContext;
|
import com.microsoft.azure.functions.ExecutionContext;
|
||||||
@@ -66,6 +67,7 @@ import org.springframework.util.StringUtils;
|
|||||||
* @author Oleg Zhurakousky
|
* @author Oleg Zhurakousky
|
||||||
* @author Chris Bono
|
* @author Chris Bono
|
||||||
* @author Christian Tzolov
|
* @author Christian Tzolov
|
||||||
|
* @author Omer Celik
|
||||||
*
|
*
|
||||||
* @since 3.2
|
* @since 3.2
|
||||||
*
|
*
|
||||||
@@ -85,6 +87,8 @@ public class FunctionInvoker<I, O> {
|
|||||||
|
|
||||||
private static JsonMapper OBJECT_MAPPER;
|
private static JsonMapper OBJECT_MAPPER;
|
||||||
|
|
||||||
|
private static final ReentrantLock globalLock = new ReentrantLock();
|
||||||
|
|
||||||
public FunctionInvoker(Class<?> configurationClass) {
|
public FunctionInvoker(Class<?> configurationClass) {
|
||||||
try {
|
try {
|
||||||
initialize(configurationClass);
|
initialize(configurationClass);
|
||||||
@@ -355,30 +359,38 @@ public class FunctionInvoker<I, O> {
|
|||||||
return new MessageHeaders(headers);
|
return new MessageHeaders(headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Double-Checked Locking Optimization was used to avoid unnecessary locking overhead.
|
||||||
|
*/
|
||||||
private static void initialize(Class<?> configurationClass) {
|
private static void initialize(Class<?> configurationClass) {
|
||||||
synchronized (FunctionInvoker.class.getName()) {
|
if (FUNCTION_CATALOG == null) {
|
||||||
if (FUNCTION_CATALOG == null) {
|
try {
|
||||||
logger.info("Initializing: " + configurationClass);
|
globalLock.lock();
|
||||||
SpringApplication builder = springApplication(configurationClass);
|
if (FUNCTION_CATALOG == null) {
|
||||||
APPLICATION_CONTEXT = builder.run();
|
logger.info("Initializing: " + configurationClass);
|
||||||
|
SpringApplication builder = springApplication(configurationClass);
|
||||||
|
APPLICATION_CONTEXT = builder.run();
|
||||||
|
|
||||||
Map<String, FunctionCatalog> mf = APPLICATION_CONTEXT.getBeansOfType(FunctionCatalog.class);
|
Map<String, FunctionCatalog> mf = APPLICATION_CONTEXT.getBeansOfType(FunctionCatalog.class);
|
||||||
if (CollectionUtils.isEmpty(mf)) {
|
if (CollectionUtils.isEmpty(mf)) {
|
||||||
OBJECT_MAPPER = new JacksonMapper(new ObjectMapper());
|
OBJECT_MAPPER = new JacksonMapper(new ObjectMapper());
|
||||||
JsonMessageConverter jsonConverter = new JsonMessageConverter(OBJECT_MAPPER);
|
JsonMessageConverter jsonConverter = new JsonMessageConverter(OBJECT_MAPPER);
|
||||||
SmartCompositeMessageConverter messageConverter = new SmartCompositeMessageConverter(
|
SmartCompositeMessageConverter messageConverter = new SmartCompositeMessageConverter(
|
||||||
Collections.singletonList(jsonConverter));
|
Collections.singletonList(jsonConverter));
|
||||||
FUNCTION_CATALOG = new SimpleFunctionRegistry(
|
FUNCTION_CATALOG = new SimpleFunctionRegistry(
|
||||||
APPLICATION_CONTEXT.getBeanFactory().getConversionService(),
|
APPLICATION_CONTEXT.getBeanFactory().getConversionService(),
|
||||||
messageConverter, OBJECT_MAPPER);
|
messageConverter, OBJECT_MAPPER);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
OBJECT_MAPPER = APPLICATION_CONTEXT.getBean(JsonMapper.class);
|
OBJECT_MAPPER = APPLICATION_CONTEXT.getBean(JsonMapper.class);
|
||||||
FUNCTION_CATALOG = mf.values().iterator().next();
|
FUNCTION_CATALOG = mf.values().iterator().next();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally {
|
||||||
|
globalLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SpringApplication springApplication(Class<?> configurationClass) {
|
private static SpringApplication springApplication(Class<?> configurationClass) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2023-2023 the original author or authors.
|
* Copyright 2023-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -19,6 +19,7 @@ package org.springframework.cloud.function.serverless.web;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
import jakarta.servlet.AsyncContext;
|
import jakarta.servlet.AsyncContext;
|
||||||
import jakarta.servlet.AsyncEvent;
|
import jakarta.servlet.AsyncEvent;
|
||||||
@@ -39,6 +40,7 @@ import org.springframework.web.util.WebUtils;
|
|||||||
* Implementation of Async context for {@link ServerlessMVC}.
|
* Implementation of Async context for {@link ServerlessMVC}.
|
||||||
*
|
*
|
||||||
* @author Oleg Zhurakousky
|
* @author Oleg Zhurakousky
|
||||||
|
* @author Omer Celik
|
||||||
*/
|
*/
|
||||||
public class ServerlessAsyncContext implements AsyncContext {
|
public class ServerlessAsyncContext implements AsyncContext {
|
||||||
private final HttpServletRequest request;
|
private final HttpServletRequest request;
|
||||||
@@ -55,6 +57,8 @@ public class ServerlessAsyncContext implements AsyncContext {
|
|||||||
|
|
||||||
private final List<Runnable> dispatchHandlers = new ArrayList<>();
|
private final List<Runnable> dispatchHandlers = new ArrayList<>();
|
||||||
|
|
||||||
|
private final ReentrantLock globalLock = new ReentrantLock();
|
||||||
|
|
||||||
|
|
||||||
public ServerlessAsyncContext(ServletRequest request, @Nullable ServletResponse response) {
|
public ServerlessAsyncContext(ServletRequest request, @Nullable ServletResponse response) {
|
||||||
this.request = (HttpServletRequest) request;
|
this.request = (HttpServletRequest) request;
|
||||||
@@ -64,7 +68,8 @@ public class ServerlessAsyncContext implements AsyncContext {
|
|||||||
|
|
||||||
public void addDispatchHandler(Runnable handler) {
|
public void addDispatchHandler(Runnable handler) {
|
||||||
Assert.notNull(handler, "Dispatch handler must not be null");
|
Assert.notNull(handler, "Dispatch handler must not be null");
|
||||||
synchronized (this) {
|
try {
|
||||||
|
this.globalLock.lock();
|
||||||
if (this.dispatchedPath == null) {
|
if (this.dispatchedPath == null) {
|
||||||
this.dispatchHandlers.add(handler);
|
this.dispatchHandlers.add(handler);
|
||||||
}
|
}
|
||||||
@@ -72,6 +77,9 @@ public class ServerlessAsyncContext implements AsyncContext {
|
|||||||
handler.run();
|
handler.run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally {
|
||||||
|
this.globalLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -102,10 +110,14 @@ public class ServerlessAsyncContext implements AsyncContext {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dispatch(@Nullable ServletContext context, String path) {
|
public void dispatch(@Nullable ServletContext context, String path) {
|
||||||
synchronized (this) {
|
try {
|
||||||
|
this.globalLock.lock();
|
||||||
this.dispatchedPath = path;
|
this.dispatchedPath = path;
|
||||||
this.dispatchHandlers.forEach(Runnable::run);
|
this.dispatchHandlers.forEach(Runnable::run);
|
||||||
}
|
}
|
||||||
|
finally {
|
||||||
|
this.globalLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
@@ -39,6 +40,7 @@ import org.springframework.util.ClassUtils;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Oleg Zhurakousky
|
* @author Oleg Zhurakousky
|
||||||
|
* @author Omer Celik
|
||||||
*/
|
*/
|
||||||
public final class JsonMasker {
|
public final class JsonMasker {
|
||||||
|
|
||||||
@@ -50,22 +52,41 @@ public final class JsonMasker {
|
|||||||
|
|
||||||
private final Set<String> keysToMask;
|
private final Set<String> keysToMask;
|
||||||
|
|
||||||
|
private static final ReentrantLock globalLock = new ReentrantLock();
|
||||||
|
|
||||||
private JsonMasker() {
|
private JsonMasker() {
|
||||||
this.keysToMask = loadKeys();
|
this.keysToMask = loadKeys();
|
||||||
this.mapper = new JacksonMapper(new ObjectMapper());
|
this.mapper = new JacksonMapper(new ObjectMapper());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized static JsonMasker INSTANCE() {
|
/**
|
||||||
|
* Double-Checked Locking Optimization was used to avoid unnecessary locking overhead.
|
||||||
|
*/
|
||||||
|
public static JsonMasker INSTANCE() {
|
||||||
if (jsonMasker == null) {
|
if (jsonMasker == null) {
|
||||||
jsonMasker = new JsonMasker();
|
try {
|
||||||
|
globalLock.lock();
|
||||||
|
if (jsonMasker == null) {
|
||||||
|
jsonMasker = new JsonMasker();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
globalLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return jsonMasker;
|
return jsonMasker;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized static JsonMasker INSTANCE(Set<String> keysToMask) {
|
public static JsonMasker INSTANCE(Set<String> keysToMask) {
|
||||||
INSTANCE().addKeys(keysToMask);
|
try {
|
||||||
return jsonMasker;
|
globalLock.lock();
|
||||||
|
INSTANCE().addKeys(keysToMask);
|
||||||
|
return jsonMasker;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
globalLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String[] getKeysToMask() {
|
public String[] getKeysToMask() {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2023-2023 the original author or authors.
|
* Copyright 2023-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
package org.springframework.cloud.function.integration.dsl;
|
package org.springframework.cloud.function.integration.dsl;
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
@@ -28,6 +29,7 @@ import org.springframework.util.Assert;
|
|||||||
* The helper class to lookup functions from the catalog in lazy manner and cache their instances.
|
* The helper class to lookup functions from the catalog in lazy manner and cache their instances.
|
||||||
*
|
*
|
||||||
* @author Artem Bilan
|
* @author Artem Bilan
|
||||||
|
* @author Omer Celik
|
||||||
*
|
*
|
||||||
* @since 4.0.3
|
* @since 4.0.3
|
||||||
*/
|
*/
|
||||||
@@ -72,16 +74,21 @@ public class FunctionLookupHelper {
|
|||||||
*/
|
*/
|
||||||
private static <T> Supplier<T> memoize(Supplier<? extends T> delegate) {
|
private static <T> Supplier<T> memoize(Supplier<? extends T> delegate) {
|
||||||
AtomicReference<T> value = new AtomicReference<>();
|
AtomicReference<T> value = new AtomicReference<>();
|
||||||
|
ReentrantLock lock = new ReentrantLock();
|
||||||
return () -> {
|
return () -> {
|
||||||
T val = value.get();
|
T val = value.get();
|
||||||
if (val == null) {
|
if (val == null) {
|
||||||
synchronized (value) {
|
try {
|
||||||
|
lock.lock();
|
||||||
val = value.get();
|
val = value.get();
|
||||||
if (val == null) {
|
if (val == null) {
|
||||||
val = delegate.get();
|
val = delegate.get();
|
||||||
value.set(val);
|
value.set(val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return val;
|
return val;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user