All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.activej.fs.cluster.ChannelByteCombiner Maven / Gradle / Ivy

Go to download

Provides tools for building efficient, scalable local, remote or clustered file servers. It utilizes ActiveJ CSP for fast and reliable file transfer.

There is a newer version: 6.0-beta2
Show newest version
/*
 * 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);
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy