io.activej.fs.cluster.ChannelByteCombiner Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of activej-fs Show documentation
Show all versions of activej-fs Show documentation
Provides tools for building efficient, scalable local, remote or clustered file servers.
It utilizes ActiveJ CSP for fast and reliable file transfer.
/*
* Copyright (C) 2020 ActiveJ LLC.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.activej.fs.cluster;
import io.activej.bytebuf.ByteBuf;
import io.activej.common.ref.RefLong;
import io.activej.csp.ChannelConsumer;
import io.activej.csp.ChannelInput;
import io.activej.csp.ChannelOutput;
import io.activej.csp.ChannelSupplier;
import io.activej.csp.dsl.WithChannelInputs;
import io.activej.csp.dsl.WithChannelOutput;
import io.activej.csp.process.AbstractCommunicatingProcess;
import io.activej.promise.Promise;
import io.activej.promise.Promises;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import static io.activej.common.Checks.checkState;
import static io.activej.eventloop.Eventloop.getCurrentEventloop;
import static io.activej.fs.cluster.FsPartitions.LOCAL_EXCEPTION;
final class ChannelByteCombiner extends AbstractCommunicatingProcess
implements WithChannelInputs, WithChannelOutput {
private final List> inputs = new ArrayList<>();
private ChannelConsumer output;
private long outputOffset;
private long errorCount;
private ChannelByteCombiner() {
}
public static ChannelByteCombiner create() {
return new ChannelByteCombiner();
}
@Override
public ChannelOutput getOutput() {
return output -> {
checkState(!isProcessStarted());
this.output = sanitize(output);
tryStart();
};
}
private void tryStart() {
if (output != null && inputs.stream().allMatch(Objects::nonNull)) {
getCurrentEventloop().post(this::startProcess);
}
}
@Override
public ChannelInput addInput() {
int index = inputs.size();
inputs.add(null);
return input -> {
inputs.set(index, input);
return getProcessCompletion();
};
}
@Override
protected void doProcess() {
Promises.all(inputs.stream().map(this::doProcessInput))
.whenException(output::closeEx)
.then($ -> output.acceptEndOfStream())
.whenComplete(this::completeProcess);
}
protected Promise doProcessInput(ChannelSupplier input) {
RefLong inputOffset = new RefLong(0);
return Promises.repeat(
() -> input.get()
.thenEx((buf, e) -> {
if (e == null) {
return Promise.of(buf);
}
if (++errorCount == inputs.size()) {
return Promise.ofException(e);
}
return Promise.of(null);
})
.then(buf -> {
if (buf == null) return Promise.of(false);
int toSkip = (int) Math.min(outputOffset - inputOffset.value, buf.readRemaining());
inputOffset.value += buf.readRemaining();
buf.moveHead(toSkip);
if (!buf.canRead()) {
buf.recycle();
return Promise.of(true);
}
outputOffset += buf.readRemaining();
return output.accept(buf).map($ -> true);
}));
}
@Override
protected void doClose(Throwable e) {
// not passing the exception to all the outputs,
// so that they wouldn't be marked dead
inputs.forEach(input -> input.closeEx(LOCAL_EXCEPTION));
output.closeEx(e);
}
}