netty5/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueDomainSocketChannel.java
Norman Maurer c10ccc5dec
Tighten contract between Channel and EventLoop by require the EventLoop on Channel construction. (#8587)
Motivation:

At the moment it’s possible to have a Channel in Netty that is not registered / assigned to an EventLoop until register(...) is called. This is suboptimal as if the Channel is not registered it is also not possible to do anything useful with a ChannelFuture that belongs to the Channel. We should think about if we should have the EventLoop as a constructor argument of a Channel and have the register / deregister method only have the effect of add a Channel to KQueue/Epoll/... It is also currently possible to deregister a Channel from one EventLoop and register it with another EventLoop. This operation defeats the threading model assumptions that are wide spread in Netty, and requires careful user level coordination to pull off without any concurrency issues. It is not a commonly used feature in practice, may be better handled by other means (e.g. client side load balancing), and therefore we propose removing this feature.

Modifications:

- Change all Channel implementations to require an EventLoop for construction ( + an EventLoopGroup for all ServerChannel implementations)
- Remove all register(...) methods from EventLoopGroup
- Add ChannelOutboundInvoker.register(...) which now basically means we want to register on the EventLoop for IO.
- Change ChannelUnsafe.register(...) to not take an EventLoop as parameter (as the EventLoop is supplied on custruction).
- Change ChannelFactory to take an EventLoop to create new Channels and introduce ServerChannelFactory which takes an EventLoop and one EventLoopGroup to create new ServerChannel instances.
- Add ServerChannel.childEventLoopGroup()
- Ensure all operations on the accepted Channel is done in the EventLoop of the Channel in ServerBootstrap
- Change unit tests for new behaviour

Result:

A Channel always has an EventLoop assigned which will never change during its life-time. This ensures we are always be able to call any operation on the Channel once constructed (unit the EventLoop is shutdown). This also simplifies the logic in DefaultChannelPipeline a lot as we can always call handlerAdded / handlerRemoved directly without the need to wait for register() to happen.

Also note that its still possible to deregister a Channel and register it again. It's just not possible anymore to move from one EventLoop to another (which was not really safe anyway).

Fixes https://github.com/netty/netty/issues/8513.
2019-01-14 20:11:13 +01:00

190 lines
6.3 KiB
Java

/*
* Copyright 2016 The Netty Project
*
* The Netty Project licenses this file to you 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.netty.channel.kqueue;
import io.netty.channel.Channel;
import io.netty.channel.ChannelConfig;
import io.netty.channel.ChannelOutboundBuffer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoop;
import io.netty.channel.unix.DomainSocketAddress;
import io.netty.channel.unix.DomainSocketChannel;
import io.netty.channel.unix.FileDescriptor;
import io.netty.channel.unix.PeerCredentials;
import io.netty.util.internal.UnstableApi;
import java.io.IOException;
import java.net.SocketAddress;
import static io.netty.channel.kqueue.BsdSocket.newSocketDomain;
@UnstableApi
public final class KQueueDomainSocketChannel extends AbstractKQueueStreamChannel implements DomainSocketChannel {
private final KQueueDomainSocketChannelConfig config = new KQueueDomainSocketChannelConfig(this);
private volatile DomainSocketAddress local;
private volatile DomainSocketAddress remote;
public KQueueDomainSocketChannel(EventLoop eventLoop) {
super(null, eventLoop, newSocketDomain(), false);
}
public KQueueDomainSocketChannel(EventLoop eventLoop, int fd) {
this(null, eventLoop, new BsdSocket(fd));
}
KQueueDomainSocketChannel(Channel parent, EventLoop eventLoop, BsdSocket fd) {
super(parent, eventLoop, fd, true);
}
@Override
protected AbstractKQueueUnsafe newUnsafe() {
return new KQueueDomainUnsafe();
}
@Override
protected DomainSocketAddress localAddress0() {
return local;
}
@Override
protected DomainSocketAddress remoteAddress0() {
return remote;
}
@Override
protected void doBind(SocketAddress localAddress) throws Exception {
socket.bind(localAddress);
local = (DomainSocketAddress) localAddress;
}
@Override
public KQueueDomainSocketChannelConfig config() {
return config;
}
@Override
protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
if (super.doConnect(remoteAddress, localAddress)) {
local = (DomainSocketAddress) localAddress;
remote = (DomainSocketAddress) remoteAddress;
return true;
}
return false;
}
@Override
public DomainSocketAddress remoteAddress() {
return (DomainSocketAddress) super.remoteAddress();
}
@Override
public DomainSocketAddress localAddress() {
return (DomainSocketAddress) super.localAddress();
}
@Override
protected int doWriteSingle(ChannelOutboundBuffer in) throws Exception {
Object msg = in.current();
if (msg instanceof FileDescriptor && socket.sendFd(((FileDescriptor) msg).intValue()) > 0) {
// File descriptor was written, so remove it.
in.remove();
return 1;
}
return super.doWriteSingle(in);
}
@Override
protected Object filterOutboundMessage(Object msg) {
if (msg instanceof FileDescriptor) {
return msg;
}
return super.filterOutboundMessage(msg);
}
/**
* Returns the unix credentials (uid, gid, pid) of the peer
* <a href=http://man7.org/linux/man-pages/man7/socket.7.html>SO_PEERCRED</a>
*/
@UnstableApi
public PeerCredentials peerCredentials() throws IOException {
return socket.getPeerCredentials();
}
private final class KQueueDomainUnsafe extends KQueueStreamUnsafe {
@Override
void readReady(KQueueRecvByteAllocatorHandle allocHandle) {
switch (config().getReadMode()) {
case BYTES:
super.readReady(allocHandle);
break;
case FILE_DESCRIPTORS:
readReadyFd();
break;
default:
throw new Error();
}
}
private void readReadyFd() {
if (socket.isInputShutdown()) {
super.clearReadFilter0();
return;
}
final ChannelConfig config = config();
final KQueueRecvByteAllocatorHandle allocHandle = recvBufAllocHandle();
final ChannelPipeline pipeline = pipeline();
allocHandle.reset(config);
readReadyBefore();
try {
readLoop: do {
// lastBytesRead represents the fd. We use lastBytesRead because it must be set so that the
// KQueueRecvByteAllocatorHandle knows if it should try to read again or not when autoRead is
// enabled.
int recvFd = socket.recvFd();
switch(recvFd) {
case 0:
allocHandle.lastBytesRead(0);
break readLoop;
case -1:
allocHandle.lastBytesRead(-1);
close(voidPromise());
return;
default:
allocHandle.lastBytesRead(1);
allocHandle.incMessagesRead(1);
readPending = false;
pipeline.fireChannelRead(new FileDescriptor(recvFd));
break;
}
} while (allocHandle.continueReading());
allocHandle.readComplete();
pipeline.fireChannelReadComplete();
} catch (Throwable t) {
allocHandle.readComplete();
pipeline.fireChannelReadComplete();
pipeline.fireExceptionCaught(t);
} finally {
readIfIsAutoRead();
readReadyFinally(config);
}
}
}
}