Product Information
ThreadContext in SAP Cloud SDK Version 4
With the release of version 4.0.0, the ThreadContext
has seen major changes in the SAP Cloud SDK.
This blog post will explain the changes we made that simplify running tasks on different threads with the ThreadContext
attached.
What is a ThreadContext?
The SAP Cloud SDK for Java provides a so-called ThreadContext
.
It serves as thread-safe storage for potentially sensitive information.
A few of the most important objects stored are:
- The current Tenant
- The current Principal (User)
- The JSON Web Token (JWT)
This information is used throughout the SAP Cloud SDK to provide features like tenant and principal isolation, JWT verification and authorization against other systems and services.
Every servlet that is received in its own thread, will receive a ThreadContext
.
This ensures different tenants and users are properly isolated.
A Typical Use Case
Let’s say we need to run a lengthy operation upon receiving a request.
The simple solution would be to block the servlet for as long as the operation takes:
@RestController
public class HelloWorldController
{
@GetMapping( "/hello" )
public String getHello()
{
final ThreadContext currentContext = ThreadContextAccessor.getCurrentContext();
database.store(currentTenant);// long-running operation
return "Hello";
}
}
Because the Spring servlet has its own thread we automatically get access to the ThreadContext
.
But our users now experience long loading times because the servlet takes a long time to answer.
We need to offload the long-running operation to another thread.
Asynchronous Operations with SAP Cloud SDK Version 3
We can call an @Async
annotated method:
@RestController
public class HelloWorldController
{
@Autowired
private DatabaseAccess databaseAccess;
@GetMapping( "/hello" )
public String getHello()
{
databaseAccess.store();
return "Hello";
}
}
The @Async
method must be public in another class to be executed in a separate thread:
@EnableAsync
@Configuration
public class DatabaseAccess
{
@Async
public String store()
{
final ThreadContext currentContext = ThreadContextAccessor.getCurrentContext();// doesn't work
database.store(currentTenant);// long-running operation
return "success";
}
}
However, this breaks the Multi Tenancy of the SAP Cloud SDK.
We cannot access the current tenant in the new thread.
Propagate the ThreadContext
We can try to use the ThreadContextExecutor
to propagate the ThreadContext
to the new thread:
@RestController
public class HelloWorldController
{
@Autowired
private DatabaseAccess databaseAccess;
@GetMapping( "/hello" )
public String getHello()
{
final ThreadContextExecutor executor = new ThreadContextExecutor();
databaseAccess.store(executor);
return "Hello";
}
}
@EnableAsync
@Configuration
public class DatabaseAccess
{
@Async
public String store( ThreadContextExecutor executor )
{
return executor.execute(() -> {
final ThreadContext currentContext = ThreadContextAccessor.getCurrentContext();// unreliable
database.store(currentTenant);// long-running operation
return "success";
});
}
}
But the access is unreliable because the ThreadContext
will get removed when the servlet is finished.
We can also see that the resulting code has a lot of boilerplate code.
Let’s take a look at how this use case would be done with SAP Cloud SDK 4.
Asynchronous Operations with SAP Cloud SDK Version 4
With version 4 you can simplify your code for running asynchronous tasks with the newly introduced ThreadContextExecutors
:
Future runningTask = ThreadContextExecutors.submit(() -> operation());
This functionality is conveniently integrated to work with Springs @Async
annotation like so:
@RestController
public class HelloWorldController
{
@Autowired
private DatabaseAccess databaseAccess;
@GetMapping( "/hello" )
public String getHello()
{
databaseAccess.store();
return "Hello";
}
}
@Configuration
public class DatabaseAccess
{
@Async
public void store()
{
database.store(TenantAccessor.getCurrentTenant());// long-running operation
}
}
This is thanks to this @Configuration
all methods annotated with @Async
will have the ThreadContext
available:
@EnableAsync
@Configuration
public class AsynchronousConfiguration implements AsyncConfigurer
{
@Override
public Executor getAsyncExecutor()
{
return ThreadContextExecutors.getExecutor();
}
}
This already comes out of the box on newly generated applications such as scp-cf-spring
.
You can read more about the @Async
functionality here.
Configuring the Executor
The ThreadContextExecutors
class leverages a single ThreadContextExecutorService
instance that can be configured.
You can create a custom ThreadContextExecutorService
, for example to use a different thread pool, via:
ThreadContextExecutorService executor = DefaultThreadContextExecutorService
.of(Executors.newFixedThreadPool(3));
// use it directly:
executor.submit(myTask);
// or set it to be used by the static ThreadContextExecutors API:
ThreadContextExecutors.setExecutor(executor);
ThreadContextExecutors.submit(myTask);
CAP Integration
When using CAP the tenant, principal, and headers are derived from the RequestContext
.
Use the cds-integration-cloud-sdk
dependency to propagate the context out of the box when using our APIs.
Please refer to the documentation on how to override existing values in the CAP context.
Summary
The new ThreadContextExecutors
allows you to run tasks on different threads with the ThreadContext
attached.
It can be integrated with Spring and CAP, and can also be configured.
For more details, please refer to the documentation or the upgrade guide.
Share Your Feedback
Do you have any questions on the new features?
Or are you struggling with the upgrade?
Don’t hesitate to share your feedback in the comments below, ask a question in the Q&A or to create a dedicated issue on our GitHub.