Merge pull request #1188 from omercelikceng/virtualThread

Change "synchronized" to reentrant lock for virtual-threads
This commit is contained in:
Oleg Zhurakousky
2024-10-15 17:47:38 +02:00
committed by GitHub
6 changed files with 112 additions and 38 deletions

View File

@@ -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();
} }
} }
} }

View File

@@ -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();
} }
} }
} }

View File

@@ -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) {

View File

@@ -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

View File

@@ -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() {

View File

@@ -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;
}; };