Asynchronous
This commit is contained in:
parent
6af215a2c9
commit
ce3eedc6eb
13
pom.xml
13
pom.xml
|
@ -56,11 +56,22 @@
|
||||||
<artifactId>common-utils</artifactId>
|
<artifactId>common-utils</artifactId>
|
||||||
<version>1.1.1</version>
|
<version>1.1.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.projectreactor</groupId>
|
||||||
|
<artifactId>reactor-core</artifactId>
|
||||||
|
<version>3.4.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.projectreactor</groupId>
|
||||||
|
<artifactId>reactor-test</artifactId>
|
||||||
|
<version>3.4.1</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<sourceDirectory>src/main/lombok</sourceDirectory>
|
<sourceDirectory>src/main/lombok</sourceDirectory>
|
||||||
<testSourceDirectory>src/main/lombok</testSourceDirectory>
|
<testSourceDirectory>src/test/lombok</testSourceDirectory>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
|
|
@ -21,41 +21,57 @@ package org.warp.filesponge;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import org.warp.commonutils.type.MultiAssociation;
|
import org.warp.filesponge.value.AsyncMultiAssociation;
|
||||||
import org.warp.filesponge.value.FileURI;
|
import org.warp.filesponge.value.FileURI;
|
||||||
import org.warp.filesponge.value.MirrorURI;
|
import org.warp.filesponge.value.MirrorURI;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
import reactor.core.scheduler.Scheduler;
|
||||||
|
import reactor.core.scheduler.Schedulers;
|
||||||
|
|
||||||
@AllArgsConstructor()
|
@AllArgsConstructor()
|
||||||
public class FileMirrorsManager {
|
public class FileMirrorsManager {
|
||||||
|
|
||||||
|
private final Scheduler fileMirrorsManagerScheduler = Schedulers.single();
|
||||||
|
|
||||||
private final MirrorAvailabilityManager mirrorAvailabilityManager;
|
private final MirrorAvailabilityManager mirrorAvailabilityManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This map must be persistent
|
* This map must be persistent
|
||||||
*/
|
*/
|
||||||
private final MultiAssociation<FileURI, MirrorURI> fileMirrors;
|
private final AsyncMultiAssociation<FileURI, MirrorURI> fileMirrors;
|
||||||
|
|
||||||
public synchronized Set<MirrorURI> getAvailableMirrors(FileURI fileURI) {
|
public Mono<Set<MirrorURI>> getAvailableMirrors(FileURI fileURI) {
|
||||||
return fileMirrors
|
return fileMirrors
|
||||||
.getLinks(fileURI)
|
.getLinks(fileURI)
|
||||||
.stream()
|
.filterWhen(mirrorAvailabilityManager::isMirrorAvailable)
|
||||||
.filter(mirrorAvailabilityManager::isMirrorAvailable)
|
.collect(Collectors.toUnmodifiableSet())
|
||||||
.collect(Collectors.toUnmodifiableSet());
|
.subscribeOn(fileMirrorsManagerScheduler);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized boolean hasAnyAvailableMirror(FileURI uri) {
|
public Mono<Boolean> hasAnyAvailableMirror(FileURI uri) {
|
||||||
return fileMirrors.getLinks(uri).stream().anyMatch(mirrorAvailabilityManager::isMirrorAvailable);
|
return fileMirrors
|
||||||
|
.getLinks(uri)
|
||||||
|
.filterWhen(mirrorAvailabilityManager::isMirrorAvailable)
|
||||||
|
.hasElements()
|
||||||
|
.subscribeOn(fileMirrorsManagerScheduler);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void addMirror(FileURI uri, MirrorURI mirrorURI) {
|
public Mono<Void> addMirror(FileURI uri, MirrorURI mirrorURI) {
|
||||||
fileMirrors.link(uri, mirrorURI);
|
return fileMirrors
|
||||||
|
.link(uri, mirrorURI)
|
||||||
|
.then()
|
||||||
|
.subscribeOn(fileMirrorsManagerScheduler);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void removeMirror(FileURI uri, MirrorURI mirrorURI) {
|
public Mono<Void> removeMirror(FileURI uri, MirrorURI mirrorURI) {
|
||||||
fileMirrors.unlink(uri, mirrorURI);
|
return fileMirrors
|
||||||
|
.unlink(uri, mirrorURI)
|
||||||
|
.then()
|
||||||
|
.subscribeOn(fileMirrorsManagerScheduler);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void unsetAllFiles() {
|
public Mono<Void> unsetAllFiles() {
|
||||||
fileMirrors.clear();
|
return fileMirrors.clear()
|
||||||
|
.subscribeOn(fileMirrorsManagerScheduler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,30 +18,40 @@
|
||||||
|
|
||||||
package org.warp.filesponge;
|
package org.warp.filesponge;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.Set;
|
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import org.warp.filesponge.value.MirrorURI;
|
import org.warp.filesponge.value.MirrorURI;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
@AllArgsConstructor(access = AccessLevel.PUBLIC)
|
@AllArgsConstructor(access = AccessLevel.PUBLIC)
|
||||||
public class MirrorAvailabilityManager {
|
public class MirrorAvailabilityManager {
|
||||||
|
|
||||||
private final Set<MirrorURI> availableMirrors = new HashSet<>();
|
private static final Object NO_VALUE = new Object();
|
||||||
|
/**
|
||||||
|
* This is a set. the value is not important
|
||||||
|
*/
|
||||||
|
private final ConcurrentHashMap<MirrorURI, Object> availableMirrors = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public synchronized void setAllMirrorsAsUnavailable() {
|
public Mono<Void> setAllMirrorsAsUnavailable() {
|
||||||
availableMirrors.clear();
|
return Mono.fromCallable(() -> {
|
||||||
|
availableMirrors.clear();
|
||||||
|
return null;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void setMirrorAvailability(MirrorURI mirrorURI, boolean available) {
|
public Mono<Void> setMirrorAvailability(MirrorURI mirrorURI, boolean available) {
|
||||||
if (available) {
|
return Mono.fromCallable(() -> {
|
||||||
availableMirrors.add(mirrorURI);
|
if (available) {
|
||||||
} else {
|
availableMirrors.put(mirrorURI, NO_VALUE);
|
||||||
availableMirrors.remove(mirrorURI);
|
} else {
|
||||||
}
|
availableMirrors.remove(mirrorURI);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized boolean isMirrorAvailable(MirrorURI mirrorURI) {
|
public Mono<Boolean> isMirrorAvailable(MirrorURI mirrorURI) {
|
||||||
return this.availableMirrors.contains(mirrorURI);
|
return Mono.fromCallable(() -> this.availableMirrors.contains(mirrorURI));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,6 @@
|
||||||
|
|
||||||
package org.warp.filesponge;
|
package org.warp.filesponge;
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.CompletionStage;
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
@ -27,6 +25,7 @@ import org.warp.filesponge.api.FileAccessor;
|
||||||
import org.warp.filesponge.value.FileContent;
|
import org.warp.filesponge.value.FileContent;
|
||||||
import org.warp.filesponge.value.FileStatus;
|
import org.warp.filesponge.value.FileStatus;
|
||||||
import org.warp.filesponge.value.FileURI;
|
import org.warp.filesponge.value.FileURI;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prevent access to other methods via casting
|
* Prevent access to other methods via casting
|
||||||
|
@ -38,17 +37,17 @@ public class SecureFileAccessor<FURI extends FileURI, FC extends FileContent> im
|
||||||
private final FileAccessor<FURI, FC> fileAccessor;
|
private final FileAccessor<FURI, FC> fileAccessor;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void delete(@NotNull FURI fileURI) {
|
public Mono<Void> delete(@NotNull FURI fileURI) {
|
||||||
fileAccessor.delete(fileURI);
|
return fileAccessor.delete(fileURI);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletionStage<Optional<FC>> getContent(@NotNull FURI fileURI, boolean offlineOnly) {
|
public Mono<FC> getContent(@NotNull FURI fileURI, boolean offlineOnly) {
|
||||||
return fileAccessor.getContent(fileURI, offlineOnly);
|
return fileAccessor.getContent(fileURI, offlineOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull FileStatus getStatus(@NotNull FURI fileURI) {
|
public @NotNull Mono<FileStatus> getStatus(@NotNull FURI fileURI) {
|
||||||
return fileAccessor.getStatus(fileURI);
|
return fileAccessor.getStatus(fileURI);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,12 +18,11 @@
|
||||||
|
|
||||||
package org.warp.filesponge.api;
|
package org.warp.filesponge.api;
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.CompletionStage;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.warp.filesponge.value.FileContent;
|
import org.warp.filesponge.value.FileContent;
|
||||||
import org.warp.filesponge.value.FileStatus;
|
import org.warp.filesponge.value.FileStatus;
|
||||||
import org.warp.filesponge.value.FileURI;
|
import org.warp.filesponge.value.FileURI;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FileAccessor can be used to access files from the client side
|
* FileAccessor can be used to access files from the client side
|
||||||
|
@ -34,23 +33,25 @@ public interface FileAccessor<FURI extends FileURI, FC extends FileContent> {
|
||||||
* Request file deletion
|
* Request file deletion
|
||||||
*
|
*
|
||||||
* @param fileURI File URI
|
* @param fileURI File URI
|
||||||
|
* @return Empty.
|
||||||
*/
|
*/
|
||||||
void delete(@NotNull FURI fileURI);
|
Mono<Void> delete(@NotNull FURI fileURI);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get file content
|
* Get file content
|
||||||
*
|
*
|
||||||
* @param fileURI File URI
|
* @param fileURI File URI
|
||||||
* @param offlineOnly true to get the file from cache
|
* @param offlineOnly true to get the file from cache
|
||||||
* @return content if found. If the request is offline the future will complete instantly
|
* @return content if found. If the request is offline the future will complete instantly.
|
||||||
|
* Can be empty
|
||||||
*/
|
*/
|
||||||
CompletionStage<Optional<FC>> getContent(@NotNull FURI fileURI, boolean offlineOnly);
|
Mono<FC> getContent(@NotNull FURI fileURI, boolean offlineOnly);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get file status
|
* Get file status
|
||||||
*
|
*
|
||||||
* @param fileURI File URI
|
* @param fileURI File URI
|
||||||
* @return status of this file
|
* @return status of this file. Cannot be empty.
|
||||||
*/
|
*/
|
||||||
@NotNull FileStatus getStatus(@NotNull FURI fileURI);
|
Mono<FileStatus> getStatus(@NotNull FURI fileURI);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,8 @@
|
||||||
package org.warp.filesponge.api;
|
package org.warp.filesponge.api;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.Optional;
|
|
||||||
import org.warp.filesponge.value.FileURI;
|
import org.warp.filesponge.value.FileURI;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FileActor sends signals to a mirror
|
* FileActor sends signals to a mirror
|
||||||
|
@ -31,25 +31,25 @@ public interface FileActor<FURI extends FileURI> {
|
||||||
* Send a "delete file" signal
|
* Send a "delete file" signal
|
||||||
*
|
*
|
||||||
* @param fileURI File URI
|
* @param fileURI File URI
|
||||||
* @return true if the signal can be sent
|
* @return true if the signal can be sent. Cannot be empty.
|
||||||
*/
|
*/
|
||||||
boolean deleteFile(FURI fileURI);
|
Mono<Boolean> deleteFile(FURI fileURI);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a "download file" signal
|
* Send a "download file" signal
|
||||||
*
|
*
|
||||||
* @param fileURI File URI
|
* @param fileURI File URI
|
||||||
* @return true if the signal can be sent
|
* @return true if the signal can be sent. Cannot be empty.
|
||||||
*/
|
*/
|
||||||
boolean downloadFile(FURI fileURI);
|
Mono<Boolean> downloadFile(FURI fileURI);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if this actor can handle signals for this file
|
* Check if this actor can handle signals for this file
|
||||||
*
|
*
|
||||||
* @param fileURI File URI
|
* @param fileURI File URI
|
||||||
* @return true if the actor can send signals related to this file
|
* @return true if the actor can send signals related to this file. Cannot be empty.
|
||||||
*/
|
*/
|
||||||
boolean canHandleFile(FURI fileURI);
|
Mono<Boolean> canHandleFile(FURI fileURI);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a "download file" signal
|
* Send a "download file" signal
|
||||||
|
@ -59,7 +59,7 @@ public interface FileActor<FURI extends FileURI> {
|
||||||
* @return empty if no pending <b>download requests</b> has been found, true if the signal can be sent, false
|
* @return empty if no pending <b>download requests</b> has been found, true if the signal can be sent, false
|
||||||
* otherwise
|
* otherwise
|
||||||
*/
|
*/
|
||||||
Optional<Boolean> downloadNextFile(Duration timeout);
|
Mono<Boolean> downloadNextFile(Duration timeout);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a "delete file" signal
|
* Send a "delete file" signal
|
||||||
|
@ -68,5 +68,5 @@ public interface FileActor<FURI extends FileURI> {
|
||||||
* <b>delete request</b> has been found, or the timeout time elapsed
|
* <b>delete request</b> has been found, or the timeout time elapsed
|
||||||
* @return empty if no pending <b>delete requests</b> has been found, true if the signal can be sent, false otherwise
|
* @return empty if no pending <b>delete requests</b> has been found, true if the signal can be sent, false otherwise
|
||||||
*/
|
*/
|
||||||
Optional<Boolean> deleteNextFile(Duration timeout);
|
Mono<Boolean> deleteNextFile(Duration timeout);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import org.jetbrains.annotations.NotNull;
|
||||||
import org.warp.filesponge.value.FileSourceAvailability;
|
import org.warp.filesponge.value.FileSourceAvailability;
|
||||||
import org.warp.filesponge.value.FileType;
|
import org.warp.filesponge.value.FileType;
|
||||||
import org.warp.filesponge.value.FileURI;
|
import org.warp.filesponge.value.FileURI;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FileSource receives responses from a mirror
|
* FileSource receives responses from a mirror
|
||||||
|
@ -32,24 +33,24 @@ public interface FileSource<FURI extends FileURI, FTYPE extends FileType> {
|
||||||
/**
|
/**
|
||||||
* Called when the mirror is online
|
* Called when the mirror is online
|
||||||
*/
|
*/
|
||||||
void onAvailable();
|
Mono<Void> onAvailable();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the mirror is unreachable
|
* Called when the mirror is unreachable
|
||||||
*/
|
*/
|
||||||
void onUnavailable();
|
Mono<Void> onUnavailable();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the mirror notifies you that a new file exists
|
* Called when the mirror notifies you that a new file exists. Cannot be empty.
|
||||||
*/
|
*/
|
||||||
boolean onNewFile(@NotNull FURI fileURI, @NotNull FTYPE fileType);
|
Mono<Boolean> onNewFile(@NotNull FURI fileURI, @NotNull FTYPE fileType);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the mirror notifies you details about a file.
|
* Called when the mirror notifies you details about a file.
|
||||||
* <p>
|
* <p>
|
||||||
* {@link FileSource#onNewFile(FURI, FTYPE)} must have been already called
|
* {@link FileSource#onNewFile(FURI, FTYPE)} must have been already called
|
||||||
*/
|
*/
|
||||||
void onFile(@NotNull FURI fileURI, @NotNull FileSourceAvailability fileAvailability, long totalSize);
|
Mono<Void> onFile(@NotNull FURI fileURI, @NotNull FileSourceAvailability fileAvailability, long totalSize);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the mirror notifies you the bytes of a part of a file.
|
* Called when the mirror notifies you the bytes of a part of a file.
|
||||||
|
@ -57,5 +58,5 @@ public interface FileSource<FURI extends FileURI, FTYPE extends FileType> {
|
||||||
* {@link FileSource#onNewFile(FURI, FTYPE)} and {@link FileSource#onFile(FURI, FileSourceAvailability, long)} must
|
* {@link FileSource#onNewFile(FURI, FTYPE)} and {@link FileSource#onFile(FURI, FileSourceAvailability, long)} must
|
||||||
* have been already called
|
* have been already called
|
||||||
*/
|
*/
|
||||||
void onFilePiece(@NotNull FURI fileURI, long offset, long size, @NotNull ByteBuffer piece);
|
Mono<Void> onFilePiece(@NotNull FURI fileURI, long offset, long size, @NotNull ByteBuffer piece);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,13 +21,14 @@ package org.warp.filesponge.api;
|
||||||
import org.warp.filesponge.SecureFileAccessor;
|
import org.warp.filesponge.SecureFileAccessor;
|
||||||
import org.warp.filesponge.value.FileContent;
|
import org.warp.filesponge.value.FileContent;
|
||||||
import org.warp.filesponge.value.FileURI;
|
import org.warp.filesponge.value.FileURI;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FileAccessor can be used to manage FileSponge and access files from the client side
|
* FileAccessor can be used to manage FileSponge and access files from the client side
|
||||||
*/
|
*/
|
||||||
public interface FileSpongeClient<FURI extends FileURI, FC extends FileContent> extends FileAccessor<FURI, FC> {
|
public interface FileSpongeClient<FURI extends FileURI, FC extends FileContent> extends FileAccessor<FURI, FC> {
|
||||||
|
|
||||||
void optimizeStorage();
|
Mono<Void> optimizeStorage();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get this instance but without special methods
|
* Get this instance but without special methods
|
||||||
|
|
|
@ -24,14 +24,15 @@ import org.warp.filesponge.value.FileContent;
|
||||||
import org.warp.filesponge.value.FileType;
|
import org.warp.filesponge.value.FileType;
|
||||||
import org.warp.filesponge.value.FileURI;
|
import org.warp.filesponge.value.FileURI;
|
||||||
import org.warp.filesponge.value.MirrorURI;
|
import org.warp.filesponge.value.MirrorURI;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
public interface FileStorage<FURI extends FileURI, FTYPE extends FileType, MURI extends MirrorURI, FC extends FileContent> {
|
public interface FileStorage<FURI extends FileURI, FTYPE extends FileType, MURI extends MirrorURI, FC extends FileContent> {
|
||||||
|
|
||||||
void newFile(@NotNull FURI fileURI, @NotNull FTYPE fileType);
|
Mono<Void> newFile(@NotNull FURI fileURI, @NotNull FTYPE fileType);
|
||||||
|
|
||||||
FC readFileData(@NotNull FURI fileURI);
|
Mono<FC> readFileData(@NotNull FURI fileURI);
|
||||||
|
|
||||||
void setFileData(@NotNull FURI fileURI, long offset, long size, ByteBuffer bytes, long totalSize);
|
Mono<Void> setFileData(@NotNull FURI fileURI, long offset, long size, ByteBuffer bytes, long totalSize);
|
||||||
|
|
||||||
boolean hasAllData(@NotNull FURI fileURI);
|
Mono<Boolean> hasAllData(@NotNull FURI fileURI);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* FileSponge
|
||||||
|
* Copyright (C) 2021 Andrea Cavalli
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.warp.filesponge.value;
|
||||||
|
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
public interface AsyncMultiAssociation<T, U> {
|
||||||
|
|
||||||
|
Mono<Boolean> link(T var1, U var2);
|
||||||
|
|
||||||
|
Mono<Boolean> unlink(T var1, U var2);
|
||||||
|
|
||||||
|
Flux<U> unlink(T var1);
|
||||||
|
|
||||||
|
Flux<T> unlinkFromSource(U var1);
|
||||||
|
|
||||||
|
default Mono<Boolean> hasAnyLink(T src) {
|
||||||
|
return this.getLinks(src).hasElements();
|
||||||
|
}
|
||||||
|
|
||||||
|
default Mono<Boolean> hasAnyLinkSource(U dest) {
|
||||||
|
return this.getLinkSources(dest).hasElements();
|
||||||
|
}
|
||||||
|
|
||||||
|
Mono<Boolean> hasLink(T var1, U var2);
|
||||||
|
|
||||||
|
Flux<U> getLinks(T var1);
|
||||||
|
|
||||||
|
Flux<T> getLinkSources(U var1);
|
||||||
|
|
||||||
|
Mono<Void> clear();
|
||||||
|
|
||||||
|
Mono<Integer> size();
|
||||||
|
|
||||||
|
Flux<T> getSources();
|
||||||
|
|
||||||
|
Flux<U> getDestinations();
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package org.warp.filesponge;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
import reactor.core.scheduler.Scheduler;
|
||||||
|
import reactor.core.scheduler.Schedulers;
|
||||||
|
import reactor.test.StepVerifier;
|
||||||
|
|
||||||
|
public class ThreadSafety {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void threadSafety() {
|
||||||
|
Scheduler schedulerSingle = Schedulers.newSingle("treadSafeScheduler");
|
||||||
|
Scheduler schedulerParallel = Schedulers.newParallel("threadUnsafeScheduler", 20);
|
||||||
|
|
||||||
|
int iterations = 500;
|
||||||
|
List<Integer> list = new LinkedList<>();
|
||||||
|
|
||||||
|
var flux = Flux.range(0, iterations)
|
||||||
|
.flatMap(s -> Mono.fromCallable(() -> {
|
||||||
|
list.add(s);
|
||||||
|
return s;
|
||||||
|
}).then(Mono.fromCallable(() -> {
|
||||||
|
list.add(1);
|
||||||
|
return s;
|
||||||
|
})).subscribeOn(schedulerSingle))
|
||||||
|
.ignoreElements()
|
||||||
|
.thenMany(Flux.defer(() -> Flux.fromIterable(list)))
|
||||||
|
.subscribeOn(schedulerParallel);
|
||||||
|
|
||||||
|
Integer[] checks = new Integer[iterations * 2];
|
||||||
|
for (int i = 0; i < iterations; i++) {
|
||||||
|
checks[i * 2] = i;
|
||||||
|
checks[i * 2 + 1] = 1;
|
||||||
|
}
|
||||||
|
StepVerifier
|
||||||
|
.create(flux)
|
||||||
|
.expectSubscription()
|
||||||
|
.expectNext(checks)
|
||||||
|
.verifyComplete();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue