/*
* Copyright 2017 GoDataDriven B.V.
*
* 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.divolte.server;
import io.undertow.util.WorkerUtils;
import org.xnio.channels.StreamSourceChannel;
import org.xnio.conduits.AbstractStreamSinkConduit;
import org.xnio.conduits.StreamSinkConduit;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.concurrent.TimeUnit;
class DelayingStreamSinkConduit extends AbstractStreamSinkConduit<StreamSinkConduit> {
private static final int NANOS_PER_SECOND = (int) TimeUnit.SECONDS.toNanos(1);
private final long blockUntil;
public DelayingStreamSinkConduit(final StreamSinkConduit next, final long delay, final TimeUnit timeUnit) {
super(next);
blockUntil = System.nanoTime() + timeUnit.toNanos(delay);
}
private boolean writesResumed;
private boolean scheduled;
@Override
public int write(final ByteBuffer src) throws IOException {
return canSend() ? super.write(src) : 0;
}
@Override
public long transferFrom(final FileChannel src, final long position, final long count) throws IOException {
return canSend() ? super.transferFrom(src, position, count) : 0;
}
@Override
public long transferFrom(final StreamSourceChannel source, final long count, final ByteBuffer throughBuffer) throws IOException {
return canSend() ? super.transferFrom(source, count, throughBuffer) : 0;
}
@Override
public long write(final ByteBuffer[] srcs, final int offs, final int len) throws IOException {
return canSend() ? super.write(srcs, offs, len) : 0;
}
@Override
public int writeFinal(final ByteBuffer src) throws IOException {
return canSend() ? super.writeFinal(src) : 0;
}
@Override
public long writeFinal(final ByteBuffer[] srcs, final int offs, final int len) throws IOException {
return canSend() ? super.writeFinal(srcs, offs, len) : 0;
}
@Override
public void resumeWrites() {
writesResumed = true;
if (canSend()) {
super.resumeWrites();
}
}
@Override
public void suspendWrites() {
writesResumed = false;
super.suspendWrites();
}
@Override
public void wakeupWrites() {
writesResumed = true;
if (canSend()) {
super.wakeupWrites();
}
}
@Override
public boolean isWriteResumed() {
return writesResumed;
}
@Override
public void awaitWritable() throws IOException {
final long remaining = blockUntil - System.nanoTime();
if (0 < remaining) {
try {
Thread.sleep(remaining / NANOS_PER_SECOND, (int)(remaining % NANOS_PER_SECOND));
} catch (final InterruptedException e) {
throw new InterruptedIOException();
}
}
super.awaitWritable();
}
@Override
public void awaitWritable(final long time, final TimeUnit timeUnit) throws IOException {
final long startTime = System.nanoTime();
final long remaining = blockUntil - startTime;
if (0 < remaining) {
try {
final long sleepNanos = Math.min(remaining, timeUnit.toNanos(time));
Thread.sleep(sleepNanos / NANOS_PER_SECOND, (int)(sleepNanos % NANOS_PER_SECOND));
} catch (final InterruptedException e) {
throw new InterruptedIOException();
}
final long elapsed = System.nanoTime() - startTime;
final long stillRemaining = timeUnit.toNanos(time) - elapsed;
if (0 < stillRemaining) {
super.awaitWritable(stillRemaining, TimeUnit.NANOSECONDS);
}
} else {
super.awaitWritable(time, timeUnit);
}
}
private boolean canSend() {
final long remaining = blockUntil - System.nanoTime();
final boolean canSend = 0 > remaining;
if (!canSend && writesResumed) {
handleWritesResumedWhenBlocked();
}
return canSend;
}
private void handleWritesResumedWhenBlocked() {
if (!scheduled) {
scheduled = true;
next.suspendWrites();
final long remaining = blockUntil - System.nanoTime();
WorkerUtils.executeAfter(getWriteThread(), () -> {
scheduled = false;
if (writesResumed) {
next.wakeupWrites();
}
}, remaining, TimeUnit.NANOSECONDS);
}
}
}