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:
Artem Bilan
2023-09-11 13:22:26 -04:00
committed by GitHub
parent 863c525790
commit 6b8d37ba30
16 changed files with 417 additions and 105 deletions

View File

@@ -19,6 +19,7 @@ package org.springframework.integration.file;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -61,9 +62,9 @@ import org.springframework.integration.support.locks.LockRegistry;
import org.springframework.integration.support.locks.PassThruLockRegistry;
import org.springframework.integration.support.management.ManageableLifecycle;
import org.springframework.integration.support.utils.IntegrationUtils;
import org.springframework.integration.util.WhileLockedProcessor;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandlingException;
import org.springframework.messaging.MessagingException;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.util.Assert;
import org.springframework.util.StreamUtils;
@@ -644,28 +645,29 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand
FileExistsMode.APPEND.equals(this.fileExistsMode)
|| FileExistsMode.APPEND_NO_FLUSH.equals(this.fileExistsMode);
File fileToCleanUpAfterCopy = tempFile;
if (append) {
final File fileToWriteTo = determineFileToWrite(resultFile, tempFile);
File fileToWriteTo = determineFileToWrite(resultFile, tempFile);
WhileLockedProcessor whileLockedProcessor = new WhileLockedProcessor(this.lockRegistry,
fileToWriteTo.getAbsolutePath()) {
try {
this.lockRegistry.executeLocked(fileToWriteTo.getAbsolutePath(),
() -> {
if (this.newFileCallback != null && !fileToWriteTo.exists()) {
this.newFileCallback.accept(fileToWriteTo, requestMessage);
}
@Override
protected void whileLocked() throws IOException {
if (FileWritingMessageHandler.this.newFileCallback != null && !fileToWriteTo.exists()) {
FileWritingMessageHandler.this.newFileCallback.accept(fileToWriteTo, requestMessage);
}
appendStreamToFile(fileToWriteTo, sourceFileInputStream);
});
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
throw new MessagingException(requestMessage, "Thread was interrupted while performing task", ex);
}
appendStreamToFile(fileToWriteTo, sourceFileInputStream);
}
};
whileLockedProcessor.doWhileLocked();
cleanUpAfterCopy(fileToWriteTo, resultFile, originalFile);
return resultFile;
fileToCleanUpAfterCopy = fileToWriteTo;
}
else {
try (InputStream inputStream = sourceFileInputStream; OutputStream outputStream =
new BufferedOutputStream(new FileOutputStream(tempFile), this.bufferSize)) {
@@ -679,9 +681,10 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand
}
outputStream.flush();
}
cleanUpAfterCopy(tempFile, resultFile, originalFile);
return resultFile;
}
cleanUpAfterCopy(fileToCleanUpAfterCopy, resultFile, originalFile);
return resultFile;
}
private void appendStreamToFile(File fileToWriteTo, InputStream sourceFileInputStream) throws IOException {
@@ -694,25 +697,29 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand
while ((bytesRead = inputStream.read(buffer)) != -1) { // NOSONAR
bos.write(buffer, 0, bytesRead);
}
if (FileWritingMessageHandler.this.appendNewLine) {
if (this.appendNewLine) {
bos.write(System.lineSeparator().getBytes());
}
}
finally {
try {
if (state == null || FileWritingMessageHandler.this.flushTask == null) {
if (bos != null) {
bos.close();
}
clearState(fileToWriteTo, state);
}
else {
state.lastWrite = System.currentTimeMillis();
cleanUpFileState(fileToWriteTo, state, bos);
}
}
private void cleanUpFileState(File fileToWriteTo, FileState state, Closeable closeable) {
try {
if (state == null || this.flushTask == null) {
if (closeable != null) {
closeable.close();
}
clearState(fileToWriteTo, state);
}
catch (IOException ex) {
else {
state.lastWrite = System.currentTimeMillis();
}
}
catch (IOException ex) {
}
}
private File handleByteArrayMessage(byte[] bytes, File originalFile, File tempFile, File resultFile,
@@ -723,20 +730,21 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand
boolean append = FileExistsMode.APPEND.equals(this.fileExistsMode)
|| FileExistsMode.APPEND_NO_FLUSH.equals(this.fileExistsMode);
WhileLockedProcessor whileLockedProcessor = new WhileLockedProcessor(this.lockRegistry,
fileToWriteTo.getAbsolutePath()) {
try {
this.lockRegistry.executeLocked(fileToWriteTo.getAbsolutePath(),
() -> {
if (append && this.newFileCallback != null && !fileToWriteTo.exists()) {
this.newFileCallback.accept(fileToWriteTo, requestMessage);
}
@Override
protected void whileLocked() throws IOException {
if (append && FileWritingMessageHandler.this.newFileCallback != null && !fileToWriteTo.exists()) {
FileWritingMessageHandler.this.newFileCallback.accept(fileToWriteTo, requestMessage);
}
writeBytesToFile(fileToWriteTo, append, bytes);
});
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
throw new MessagingException(requestMessage, "Thread was interrupted while performing task", ex);
}
writeBytesToFile(fileToWriteTo, append, bytes);
}
};
whileLockedProcessor.doWhileLocked();
cleanUpAfterCopy(fileToWriteTo, resultFile, originalFile);
return resultFile;
}
@@ -752,19 +760,7 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand
}
}
finally {
try {
if (state == null || this.flushTask == null) {
if (bos != null) {
bos.close();
}
clearState(fileToWriteTo, state);
}
else {
state.lastWrite = System.currentTimeMillis();
}
}
catch (IOException ex) {
}
cleanUpFileState(fileToWriteTo, state, bos);
}
}
@@ -776,20 +772,21 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand
boolean append = FileExistsMode.APPEND.equals(this.fileExistsMode)
|| FileExistsMode.APPEND_NO_FLUSH.equals(this.fileExistsMode);
WhileLockedProcessor whileLockedProcessor = new WhileLockedProcessor(this.lockRegistry,
fileToWriteTo.getAbsolutePath()) {
@Override
protected void whileLocked() throws IOException {
if (append && FileWritingMessageHandler.this.newFileCallback != null && !fileToWriteTo.exists()) {
FileWritingMessageHandler.this.newFileCallback.accept(fileToWriteTo, requestMessage);
}
try {
this.lockRegistry.executeLocked(fileToWriteTo.getAbsolutePath(),
() -> {
if (append && this.newFileCallback != null && !fileToWriteTo.exists()) {
this.newFileCallback.accept(fileToWriteTo, requestMessage);
}
writeStringToFile(fileToWriteTo, append, content);
}
};
whileLockedProcessor.doWhileLocked();
writeStringToFile(fileToWriteTo, append, content);
});
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
throw new MessagingException(requestMessage, "Thread was interrupted while performing task", ex);
}
cleanUpAfterCopy(fileToWriteTo, resultFile, originalFile);
return resultFile;
@@ -806,19 +803,7 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand
}
}
finally {
try {
if (state == null || FileWritingMessageHandler.this.flushTask == null) {
if (writer != null) {
writer.close();
}
clearState(fileToWriteTo, state);
}
else {
state.lastWrite = System.currentTimeMillis();
}
}
catch (IOException ex) {
}
cleanUpFileState(fileToWriteTo, state, writer);
}
}