/**
* Copyright 2013, Landz and its contributors. All rights reserved.
*
* 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 z.net;
import z.channel.*;
import z.function.Pipeline;
import z.offheap.buffer.Buffer;
import z.util.concurrent.ThreadLocalPool;
import java.util.function.IntConsumer;
import java.util.stream.IntStream;
import static z.offheap.zmalloc.Allocator.*;
import static z.util.Contracts.contract;
import static z.util.Unsafes.*;
import static z.znr.Errno.*;
import static z.znr.socket.Sockets.*;
import static z.znr.event.EPollEvents.*;
/**
* AsyncIOThreadPool, which holds a pool of {@link Thread}s, is used to schedule
* socket IO's execution.
*
* design contract:
* <p> 1. only concern related events when ready to concern those
*
* TODO: MEMCHK in comment may be replaced with annotations with check toolings
*
*/
public class AsyncIOThreadPool {
public static final int SIZE_NET_READ_BUFFER = 4*1024;
public static final int SIZE_NET_WRITE_BUFFER = 4*1024;
public static final IntConsumer NULL_CONSUMER = (fd)->{};
public final int poolSize;
private final AsyncIOWorkerThread[] workers;
private final EventPoll eventPoll;
/** NOTE: pipeline is not thread-safe. */
private final Pipeline<PipelineContext, PipelineContext> pipeline;
private final ThreadLocalPool<PipelineContext> pipelineContextPool;
private final IntConsumer cleaner;
public AsyncIOThreadPool(
int poolSize,
Pipeline<PipelineContext, PipelineContext> pipeline,
IntConsumer cleaner) {
contract(()->poolSize>0);//TODO
this.poolSize = poolSize;
this.pipeline = pipeline;
this.workers = new AsyncIOWorkerThread[poolSize];
this.eventPoll = new EventPoll();
//TODO: MAX_SUPPORTED_SOCKS is OK?
this.pipelineContextPool =
new ThreadLocalPool(NetModule.MAX_SUPPORTED_SOCKS,PipelineContext::new);
IntStream.range(0,poolSize)
.forEach(i -> workers[i] = new AsyncIOWorkerThread(i));
this.cleaner = cleaner;
}
/**
* TODO
* note: the back queue is bounded, so this async may be blocked
* when the work queue can not accept the asyncors
*
*/
void accept(int sockfd, int workerIndex) {
workers[workerIndex].clientSocks.send(sockfd);
}
public final void start() {
for (Thread t : workers) {
t.start();
}
}
public IntConsumer cleaner() {
return cleaner;
}
//======================================================================
//contracts trick
protected static final boolean ENABLE_CONTRACTS = true;
/**
* AsyncIOWorkerThread, provide the worker thread implementation for AsyncPool.
*
*/
class AsyncIOWorkerThread extends Thread {
private final int id;
final IntHyperLoop clientSocks;
final IntHyperLoop.OutPort clientSocksPort;
final MPMCQueue activeSocks;
AsyncIOWorkerThread(int id) {
super("AsyncIOWorkerThread#"+id);
this.id = id;
this.clientSocks = new IntHyperLoop(1024*16);//TODO
this.clientSocksPort = clientSocks.createOutPort();
this.activeSocks = new MPMCQueue(1024*16);
}
/**
* main run loop
*/
public void run() {
int suc = 0;//FIXME debug
try {
for(;;) {
//accept
while (clientSocksPort.isReceivable()) {
int sockfd = clientSocksPort.receive();
//FIXME
if (ENABLE_CONTRACTS) {
if (sockfd>=NetModule.MAX_SUPPORTED_SOCKS) {
throw new IllegalArgumentException(String.format(
"the socket file descriptor(=%,d) is larger than " +
"MAX_SUPPORTED_SOCKS %,d",
sockfd, NetModule.MAX_SUPPORTED_SOCKS));
}
}
//FIXME: investigate performance for non-removal
//MEMCHK#1: release after fd closed
long pointer = allocate(POINTERED_AREA_SIZE);
setFileDescriptorForPointer(pointer, sockfd);
setBufferAddressForPointer(pointer, 0L);
setBufferSizeForPointer(pointer, 0);
setWriteOffsetForPointer(pointer, 0L);
//XXX: ignore the failing when non-removal
suc = eventPoll.addForRead(sockfd, pointer);//FIXME: log
//FIXME: so, suc = -EEXIST is allowed
}
//poll
EventArray events = eventPoll.pollNonBlock();
if (events!=null) {
int numEvents = events.availableNumEvents;
for (int i = 0; i < numEvents; i++) {
//note: here just reuse the partial of events
long event = events.getEventAddress(i);
activeSocks.offer(event);//FIXME log
}
long event;
//event queue has provided mem barrier
while ( MPMCQueue.NULL!=(event=activeSocks.poll()) ) {
int mask = getEventMask(event);
if (mask == EPOLLIN) {
doRead(event);
} else if (mask == EPOLLOUT) {
doWrite(event);
} else {//FIXME
long pointer = getPointer(event);
int fd = getFileDescriptorForPointer(pointer);
long buffer = getBufferAddressForPointer(pointer);
suc = close(fd);//FIXME for debug
free(pointer);//match MEMCHK#1
}
}
events.close();
} else {
//FIXME
UNSAFE.park(false, 100L);
}
}
} catch (Throwable ex) {
ex.printStackTrace();//FIXME
}
}
private void doRead(long event) {
try (ThreadLocalPool.Item<PipelineContext> contextItem
= pipelineContextPool.item()) {
PipelineContext pContext = contextItem.get();
long pointer = getPointer(event);
int fd = getFileDescriptorForPointer(pointer);
Buffer inBuffer = (Buffer) pContext.inBuffer;
if (inBuffer.address() == 0L) {
//MEMCHK#2: free after business logic done, but this may be changed
// after the read side design changed
inBuffer.address(allocate(SIZE_NET_READ_BUFFER));
inBuffer.capacity(SIZE_NET_READ_BUFFER);
}
long n = read(fd, inBuffer.address(), SIZE_NET_READ_BUFFER);
if (n == SIZE_NET_READ_BUFFER) {
//read again
//FIXME: doesn't work now
activeSocks.offer(event);//FIXME log - should not be false
} else if (n > 0) {
//FIXME
inBuffer.skipWriteTo(n);
} else if (n == -EAGAIN || n == 0) {
//FIXME do noting? n==0?
eventPoll.concernRead(fd, pointer);
return;
} else if (n < 0) {
//FIXME: more treatment for different errnos
//FIXME: do clean, outBuffer aslo need be freed
// after the read side design changed
int suc = close(fd);//FIXME for debug
free(pointer);//MEMCHK#1
free(inBuffer.address());//MEMCHK#2
inBuffer.address(0L);
//FIXME log
return;
}
//
Buffer outBuffer = (Buffer) pContext.outBuffer;
if (outBuffer.address() == 0L) {
//MEMCHK#3: free after write
outBuffer.address(allocate(SIZE_NET_WRITE_BUFFER));
outBuffer.capacity(SIZE_NET_WRITE_BUFFER);
}
//FIXME business logic
try {
pipeline.apply(pContext);
}catch (Throwable t) {
//FIXME log
//lastly do clean
free(inBuffer.address());//MEMCHK#2
inBuffer.address(0L);
inBuffer.reset();
free(outBuffer.address());//MEMCHK#3
outBuffer.address(0L);
outBuffer.reset();
}
setBufferAddressForPointer(pointer, pContext.outBuffer.address());
setBufferSizeForPointer(pointer, (int) pContext.outBuffer.writeCursor());
setWriteOffsetForPointer(pointer, 0L);
doWrite(event);
//lastly do clean
//FIXME should be protected against potential exceptions
free(inBuffer.address());//MEMCHK#2
inBuffer.address(0L);
inBuffer.reset();
//NOTE: outBuffer is not freed here
outBuffer.address(0L);
outBuffer.reset();
}
}
private void doWrite(long event) {
long pointer = getPointer(event);
int fd = getFileDescriptorForPointer(pointer);
long buffer = getBufferAddressForPointer(pointer);
int size = getBufferSizeForPointer(pointer);
long offset = getWriteOffsetForPointer(pointer);
//FIXME: this is an unusual case. how about a special dealing?
if (size==0) {
eventPoll.concernRead(fd, pointer);
return;
}
long count = size-offset;
long n = write(fd, buffer+offset, count);//FIXME: suc for debug, log
if (n==count) {
free(buffer);//MEMCHK#3
eventPoll.concernRead(fd, pointer);
} else if (n>0) {
setWriteOffsetForPointer(pointer, n);
eventPoll.concernWrite(fd, pointer);
} else if (n==-EAGAIN || n==0) {
eventPoll.concernWrite(fd, pointer);
} else {
//do clean
int suc = close(fd);
free(pointer);//MEMCHK#1
free(buffer);//MEMCHK#3
}
}
}
//=====================================================
//helpers for EventPoll
private static final boolean isErrorEvent(int mask) {
return (mask & ( EPOLLERR | EPOLLHUP ) )!=0;
}
private static final boolean isCloseEvent(int mask) {
return (mask & EPOLLRDHUP )!=0;
}
//=====================================================
//helper for the pointer (to event data "struct")
private static final int POINTERED_AREA_SIZE = 24;
private static final int getFileDescriptorForPointer(long pointer) {
return UNSAFE.getInt(pointer);
}
private static final void setFileDescriptorForPointer(long pointer, int fd) {
UNSAFE.putInt(pointer, fd);
}
private static final long getBufferAddressForPointer(long pointer) {
return UNSAFE.getLong(pointer+4);
}
private static final void setBufferAddressForPointer(long pointer, long bufferAddress) {
UNSAFE.putLong(pointer+4, bufferAddress);
}
private static final int getBufferSizeForPointer(long pointer) {
return UNSAFE.getInt(pointer+12);
}
private static final void setBufferSizeForPointer(long pointer, int size) {
UNSAFE.putInt(pointer+12, size);
}
private static final long getWriteOffsetForPointer(long pointer) {
return UNSAFE.getLong(pointer + 16);
}
private static final void setWriteOffsetForPointer(long pointer, long offset) {
UNSAFE.putLong(pointer + 16, offset);
}
}