GH-2971: Add LockRegistry.executeLocked() API (#8729)
* GH-2971: Add `LockRegistry.executeLocked()` API Fixes https://github.com/spring-projects/spring-integration/issues/2971 * Following best practice and well-known patterns with `Jdbc`, `Rest` or `Jms` templates, introduce `default` methods into `LockRegistry` interface to make it easier to perform tasks when within a lock. * Since all the required logic is now covered by those `LockRegistry.executeLocked()` methods, there is no need in the dedicated abstract `WhileLockedProcessor` class. Deprecated it for removal in the next version * Use a new `LockRegistry.executeLocked()` API in the `FileWritingMessageHandler` instead of just deprecated `WhileLockedProcessor` * To satisfy Java limitations for checked lambdas, introduce `CheckedCallable` and `CheckedRunnable` utilities similar to interfaces in the `io.micrometer.observation.Observation` * Change existing `CheckedFunction` to expose extra generic argument for `Throwable` * Add dedicated chapter for distributed lock into docs * Fix some links and typos in the docs * * Fix Javadoc for `CheckedFunction` * Fix language in docs Co-authored-by: Gary Russell <grussell@vmware.com> --------- Co-authored-by: Gary Russell <grussell@vmware.com>
This commit is contained in:
@@ -94,6 +94,7 @@
|
||||
** xref:message-history.adoc[]
|
||||
** xref:message-store.adoc[]
|
||||
** xref:meta-data-store.adoc[]
|
||||
** xref:distributed-locks.adoc[]
|
||||
** xref:control-bus.adoc[]
|
||||
** xref:shutdown.adoc[]
|
||||
** xref:graph.adoc[]
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
[[distributed-locks]]
|
||||
= Distributed Locks
|
||||
|
||||
In many situations the action against some context (or even single message) has to be performed in an exclusive manner.
|
||||
One example is an aggregator component where we have to check the message group state for the current message to determine whether we can release the group or just add that message for future consideration.
|
||||
For this purpose Java provides an API with `java.util.concurrent.locks.Lock` implementations.
|
||||
However, the problem becomes more complex when an application is distributed and/or run in the cluster.
|
||||
The locking in this case is challenging and requires some shared state and its specific approach to achieve the exclusivity requirement.
|
||||
|
||||
Spring Integration provides a `LockRegistrty` abstraction with an in-memory `DefaultLockRegistry` implementation based on the `ReentrantLock` API.
|
||||
The `obtain(Object)` method of the `LockRegistrty` requires a `lock key` for specific context.
|
||||
For example, an aggregator uses a `correlationKey` to lock operations around its group.
|
||||
This way different locks can be used concurrently.
|
||||
This `obtain(Object)` method returns a `java.util.concurrent.locks.Lock` instance (depending on the `LockRegistry` implementation), therefore the rest of the logic is the same as standard Java Concurrency algorithm.
|
||||
|
||||
Starting with version 6.2, the `LockRegistry` provides an `executeLocked()` API (`default` methods in this interface) to perform some task while locked.
|
||||
The behavior of this API is similar to well-known `JdbcTemplate`, `JmsTemplate` or `RestTemplate`.
|
||||
The following example demonstrates the usage of this API:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
LockRegistry registry = new DefaultLockRegistry();
|
||||
...
|
||||
registry.executeLocked("someLockKey", () -> someExclusiveResourceCall());
|
||||
----
|
||||
|
||||
The method rethrows an exception from the task call, throws an `InterruptedException` if `Lock` is interrupted.
|
||||
In addition, a variant with `Duration` throws a `java.util.concurrent.TimeoutException` when `lock.tryLock()` returns `false`.
|
||||
|
||||
Spring Integration provides these `LockRegistrty` implementations for distributed locks:
|
||||
|
||||
* xref:hazelcast.adoc#hazelcast-lock-registry[Hazelcast]
|
||||
* xref:jdbc/lock-registry.adoc[JDBC]
|
||||
* xref:redis.adoc#redis-lock-registry[Redis]
|
||||
* xref:zookeeper.adoc#zk-lock-registry[Zookeeper]
|
||||
|
||||
https://github.com/spring-projects/spring-integration-aws[Spring Integration AWS] extension also implements a `DynamoDbLockRegistry`.
|
||||
@@ -27,7 +27,7 @@ The following pair of examples show how to add a reference to a message store fo
|
||||
.Aggregator
|
||||
[source,xml]
|
||||
----
|
||||
<int:aggregator … message-store="refToMessageStore"/>
|
||||
<int:aggregator message-store="refToMessageStore"/>
|
||||
----
|
||||
|
||||
By default, messages are stored in-memory by using `o.s.i.store.SimpleMessageStore`, an implementation of `MessageStore`.
|
||||
@@ -151,7 +151,7 @@ The `MessageGroupStore` exposes a `setGroupCondition(Object groupId, String cond
|
||||
For this purpose a `setGroupConditionSupplier(BiFunction<Message<?>, String, String>)` option has been added to the `AbstractCorrelatingMessageHandler`.
|
||||
This function is evaluated against each message after it has been added to the group as well as the existing condition of the group.
|
||||
The implementation may decide to return a new value, the existing value, or reset the target condition to `null`.
|
||||
The value for a `condition` can be a JSON, SpEL expression, number or anything what can be serialized as a string and parsed afterwards.
|
||||
The value for a `condition` can be a JSON, SpEL expression, number or anything what can be serialized as a string and parsed afterward.
|
||||
For example, the `FileMarkerReleaseStrategy` from the xref:file/aggregator.adoc[File Aggregator] component, populates a condition into a group from the `FileHeaders.LINE_COUNT` header of the `FileSplitter.FileMarker.Mark.END` message and consults with it from its `canRelease()` comparing a group size with the value in this condition.
|
||||
This way it doesn't iterate all the messages in group to find a `FileSplitter.FileMarker.Mark.END` message with the `FileHeaders.LINE_COUNT` header.
|
||||
It also allows the end marker to arrive at the aggregator before all the other records; for example when processing a file in a multi-threaded environment.
|
||||
|
||||
@@ -15,7 +15,7 @@ If you need to persist metadata between application context restarts, the framew
|
||||
|
||||
* `PropertiesPersistingMetadataStore`
|
||||
* xref:hazelcast.adoc#hazelcast-metadata-store[Hazelcast Metadata Store]
|
||||
* xref:jdbc.adoc#jdbc-metadata-store[JDBC Metadata Store]
|
||||
* xref:jdbc/metadata-store.adoc[JDBC Metadata Store]
|
||||
* xref:mongodb.adoc#mongodb-metadata-store[MongoDB Metadata Store]
|
||||
* xref:redis.adoc#redis-metadata-store[Redis Metadata Store]
|
||||
* xref:zookeeper.adoc#zk-metadata-store[Zookeeper Metadata Store]
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
Spring Integration provides a generic router.
|
||||
You can use it for general-purpose routing (as opposed to the other routers provided by Spring Integration, each of which has some form of specialization).
|
||||
|
||||
[[configuring-a-content-based-router-with-xml]]
|
||||
== Configuring a Content-based Router with XML
|
||||
The following section explains a router configuration with an XML components.
|
||||
|
||||
The `router` element provides a way to connect a router to an input channel and also accepts the optional `default-output-channel` attribute.
|
||||
The `ref` attribute references the bean name of a custom router implementation (which must extend `AbstractMessageRouter`).
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
Sometimes, the routing logic may be simple, and writing a separate class for it and configuring it as a bean may seem like overkill.
|
||||
As of Spring Integration 2.0, we offer an alternative that lets you use SpEL to implement simple computations that previously required a custom POJO router.
|
||||
|
||||
NOTE: For more information about the Spring Expression Language, see the https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#expressions[relevant chapter in the Spring Framework Reference Guide].
|
||||
NOTE: For more information about the Spring Expression Language, see the https://docs.spring.io/spring-framework/reference/core/expressions.html[relevant chapter in the Spring Framework Reference Guide].
|
||||
|
||||
Generally, a SpEL expression is evaluated and its result is mapped to a channel, as the following example shows:
|
||||
|
||||
@@ -69,6 +69,6 @@ In the above configuration, if the message includes a header with a name of 'cha
|
||||
You may also find collection projection and collection selection expressions useful when you need to select multiple channels.
|
||||
For further information, see:
|
||||
|
||||
* https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#expressions-collection-projection[Collection Projection]
|
||||
* https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#expressions-collection-selection[Collection Selection]
|
||||
* https://docs.spring.io/spring-framework/reference/core/expressions/language-ref/collection-projection.html[Collection Projection]
|
||||
* https://docs.spring.io/spring-framework/reference/core/expressions/language-ref/collection-selection.html[Collection Selection]
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ The following example shows how to configure the `<scatter-gather>` endpoint by
|
||||
<1> The id of the endpoint.
|
||||
The `ScatterGatherHandler` bean is registered with an alias of `id + '.handler'`.
|
||||
The `RecipientListRouter` bean is registered with an alias of `id + '.scatterer'`.
|
||||
The `AggregatingMessageHandler`bean is registered with an alias of `id + '.gatherer'`.
|
||||
The `AggregatingMessageHandler` bean is registered with an alias of `id + '.gatherer'`.
|
||||
Optional.
|
||||
(The `BeanFactory` generates a default `id` value.)
|
||||
<2> Lifecycle attribute signaling whether the endpoint should be started during application context initialization.
|
||||
@@ -171,7 +171,7 @@ This way all other sub-flows will work for nothing and their replies are going t
|
||||
This might be an expected behavior sometimes, but in most cases it would be better to handle the error in the particular sub-flow without impacting all others and the expectations in the gatherer.
|
||||
|
||||
Starting with version 5.1.3, the `ScatterGatherHandler` is supplied with the `errorChannelName` option.
|
||||
It is populated to the `errorChannel` header of the scatter message and is used in the when async error happens or can be used in the regular synchronous sub-flow for directly sending an error message.
|
||||
It is populated to the `errorChannel` header of the scatter message and is used when an async error happens or can be used in the regular synchronous sub-flow for directly sending an error message.
|
||||
|
||||
The sample configuration below demonstrates async error handling by returning a compensation message:
|
||||
|
||||
|
||||
@@ -31,13 +31,16 @@ See xref:debezium.adoc[Debezium Support] for more information.
|
||||
See xref:endpoint.adoc#endpoint-pollingconsumer[Polling Consumer] for more information.
|
||||
|
||||
- Java, Groovy and Kotlin DSLs have now context-specific methods in the `IntegrationFlowDefinition` with a single `Consumer` argument to configure an endpoint and its handler with one builder and readable options.
|
||||
See, for example, `transformWith()`, `splitWith()` in xref:dsl.adoc#java-dsl[ Java DSL Chapter].
|
||||
See, for example, `transformWith()`, `splitWith()` in xref:dsl.adoc#java-dsl[Java DSL Chapter].
|
||||
|
||||
- A new `spring.integration.endpoints.defaultTimeout` global property has been introduced to override the default 30 seconds timeout for all the endpoints in the application.
|
||||
See xref:configuration/global-properties.adoc[Global Properties] for more information.
|
||||
|
||||
- The `@MessagingGateway` and `GatewayEndpointSpec` provided by the Java DSL now expose the `errorOnTimeout` property of the internal `MethodInvocationGateway` extension of the `MessagingGatewaySupport`.
|
||||
See xref:gateway.adoc#gateway-no-response[ Gateway Behavior When No response Arrives] for more information.
|
||||
See xref:gateway.adoc#gateway-no-response[Gateway Behavior When No response Arrives] for more information.
|
||||
|
||||
- The `LockRegistry` provides template-like API to execute provided task while locked.
|
||||
See xref:distributed-locks.adoc[Distributed Locks] for more information.
|
||||
|
||||
[[x6.2-websockets]]
|
||||
=== WebSockets Changes
|
||||
@@ -55,7 +58,7 @@ See xref:kafka.adoc#kafka-inbound-pollable[Kafka Inbound Channel Adapter] for mo
|
||||
[[x6.2-jdbc]]
|
||||
=== JDBC Support Changes
|
||||
|
||||
The `JdbcMessageStore`, `JdbcChannelMessageStore`, `JdbcMetadataStore`, and `DefaultLockRepository` implement `SmartLifecycle` and perform a`SELECT COUNT` query, on their respective tables, in the `start()` method to ensure that the required table (according to the provided prefix) is present in the target database.
|
||||
The `JdbcMessageStore`, `JdbcChannelMessageStore`, `JdbcMetadataStore`, and `DefaultLockRepository` implement `SmartLifecycle` and perform a `SELECT COUNT` query, on their respective tables, in the `start()` method to ensure that the required table (according to the provided prefix) is present in the target database.
|
||||
See xref:jdbc/message-store.adoc#jdbc-db-init[Initializing the Database] for more information.
|
||||
|
||||
[[x6.2-mongodb]]
|
||||
|
||||
Reference in New Issue
Block a user