/*
* Copyright 2016 The Simple File Server Authors
*
* 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 org.sfs.io;
import io.vertx.core.Context;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import org.sfs.util.AtomicIntMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class WriteQueueSupport<A> {
private final long maxWrites;
private final long lowWater;
private final AtomicIntMap<A> queues = new AtomicIntMap<>();
private final ConcurrentMap<A, ContextHandlerList> writeQueueEmptyHandlers = new ConcurrentHashMap<>();
private final Set<ContextHandler> drainHandlers = Collections.newSetFromMap(new ConcurrentHashMap<>());
public WriteQueueSupport(long maxWrites) {
this.maxWrites = maxWrites;
this.lowWater = maxWrites / 2;
}
public long getMaxWrites() {
return maxWrites;
}
public long getLowWater() {
return lowWater;
}
public void drainHandler(Context context, Handler<Void> handler) {
drainHandlers.add(new ContextHandler(context, handler));
notifyDrainHandlers();
}
public WriteQueueSupport<A> remove(A entity) {
queues.remove(entity);
notifyDrainHandlers();
notifyEmptyHandlers(entity);
return this;
}
public boolean writeQueueFull() {
return queues.sum() >= maxWrites;
}
public void incrementWritesOutstanding(A writer, int delta) {
queues.addAndGet(writer, delta);
}
public void decrementWritesOutstanding(A writer, int delta) {
queues.addAndGet(writer, -delta);
queues.removeIfLteZero(writer);
notifyDrainHandlers();
notifyEmptyHandlers(writer);
}
public void emptyHandler(A writer, Context context, Handler<Void> handler) {
ContextHandler contextHandler = new ContextHandler(context, handler);
while (true) {
ContextHandlerList existing = writeQueueEmptyHandlers.get(writer);
if (existing != null) {
ContextHandlerList updated = new ContextHandlerList(new ArrayList<>(existing.getList()));
updated.add(contextHandler);
if (writeQueueEmptyHandlers.replace(writer, existing, updated)) {
break;
}
} else {
ContextHandlerList updated = new ContextHandlerList(Collections.singletonList(contextHandler));
if (writeQueueEmptyHandlers.putIfAbsent(writer, updated) == null) {
break;
}
}
}
notifyEmptyHandlers(writer);
}
public boolean writeQueueEmpty(A writer) {
return queues.get(writer) <= 0;
}
public long getSize() {
return queues.sum();
}
protected void notifyEmptyHandlers(A writer) {
if (queues.get(writer) <= 0) {
ContextHandlerList contextHandlerList = writeQueueEmptyHandlers.remove(writer);
if (contextHandlerList != null) {
for (ContextHandler contextHandler : contextHandlerList.getList()) {
contextHandler.dispatch();
}
}
}
}
protected void notifyDrainHandlers() {
int size = queues.sum();
if (size <= lowWater) {
Iterator<ContextHandler> iterator = drainHandlers.iterator();
while (iterator.hasNext()) {
ContextHandler contextHandler = iterator.next();
iterator.remove();
contextHandler.dispatch();
}
}
}
private static class ContextHandler {
private final Context context;
private final Handler<Void> handler;
public ContextHandler(Context context, Handler<Void> handler) {
this.context = context;
this.handler = handler;
}
public void dispatch() {
context.runOnContext(event -> handler.handle(null));
}
}
private static class ContextHandlerList {
private final List<ContextHandler> list;
public ContextHandlerList(List<ContextHandler> list) {
this.list = list;
}
public List<ContextHandler> getList() {
return list;
}
public void add(ContextHandler contextHandler) {
list.add(contextHandler);
}
}
}