return http->getConn();
}
-/**
- * This routine should be called to grow the in.buf and then
- * call Comm::Read().
- */
-void
-ConnStateData::readSomeData()
-{
- if (reading())
- return;
-
- debugs(33, 4, HERE << clientConnection << ": reading request...");
-
- // we can only read if there is more than 1 byte of space free
- if (Config.maxRequestBufferSize - in.buf.length() < 2)
- return;
-
- typedef CommCbMemFunT<ConnStateData, CommIoCbParams> Dialer;
- reader = JobCallback(33, 5, Dialer, this, ConnStateData::clientReadRequest);
- Comm::Read(clientConnection, reader);
-}
-
void
ClientSocketContext::removeFromConnectionList(ConnStateData * conn)
{
unpinConnection(true);
- if (Comm::IsConnOpen(clientConnection))
- clientConnection->close();
+ Server::swanSong(); // closes the client connection
#if USE_AUTH
// NP: do this bit after closing the connections to avoid side effects from unwanted TCP RST
setAuth(NULL, "ConnStateData::SwanSong cleanup");
#endif
- BodyProducer::swanSong();
flags.swanSang = true;
}
if (bodyPipe != NULL)
stopProducingFor(bodyPipe, false);
+ delete bodyParser; // TODO: pool
+
#if USE_OPENSSL
delete sslServerBump;
#endif
if (!stoppedReceiving()) {
if (const int64_t expecting = mayNeedToReadMoreBody()) {
debugs(33, 5, HERE << "must still read " << expecting <<
- " request body bytes with " << in.buf.length() << " unused");
+ " request body bytes with " << inBuf.length() << " unused");
return; // wait for the request receiver to finish reading
}
}
ConnStateData::abortRequestParsing(const char *const uri)
{
ClientHttpRequest *http = new ClientHttpRequest(this);
- http->req_sz = in.buf.length();
+ http->req_sz = inBuf.length();
http->uri = xstrdup(uri);
setLogUri (http, uri);
ClientSocketContext *context = new ClientSocketContext(clientConnection, http);
{
/* Attempt to parse the first line; this will define where the method, url, version and header begin */
{
- const bool parsedOk = hp->parse(csd->in.buf);
+ const bool parsedOk = hp->parse(csd->inBuf);
if (csd->port->flags.isIntercepted() && Config.accessList.on_unsupported_protocol)
- csd->preservedClientData = csd->in.buf;
+ csd->preservedClientData = csd->inBuf;
// sync the buffers after parsing.
- csd->in.buf = hp->remaining();
+ csd->inBuf = hp->remaining();
if (hp->needsMoreData()) {
debugs(33, 5, "Incomplete request, waiting for end of request line");
return result;
}
-bool
-ConnStateData::In::maybeMakeSpaceAvailable()
-{
- if (buf.spaceSize() < 2) {
- const SBuf::size_type haveCapacity = buf.length() + buf.spaceSize();
- if (haveCapacity >= Config.maxRequestBufferSize) {
- debugs(33, 4, "request buffer full: client_request_buffer_max_size=" << Config.maxRequestBufferSize);
- return false;
- }
- if (haveCapacity == 0) {
- // haveCapacity is based on the SBuf visible window of the MemBlob buffer, which may fill up.
- // at which point bump the buffer back to default. This allocates a new MemBlob with any un-parsed bytes.
- buf.reserveCapacity(CLIENT_REQ_BUF_SZ);
- } else {
- const SBuf::size_type wantCapacity = min(static_cast<SBuf::size_type>(Config.maxRequestBufferSize), haveCapacity*2);
- buf.reserveCapacity(wantCapacity);
- }
- debugs(33, 2, "growing request buffer: available=" << buf.spaceSize() << " used=" << buf.length());
- }
- return (buf.spaceSize() >= 2);
-}
-
void
ConnStateData::addContextToQueue(ClientSocketContext * context)
{
return result;
}
-int
+bool
ConnStateData::connFinishedWithConn(int size)
{
if (size == 0) {
- if (getConcurrentRequestCount() == 0 && in.buf.isEmpty()) {
+ if (getConcurrentRequestCount() == 0 && inBuf.isEmpty()) {
/* no current or pending requests */
debugs(33, 4, HERE << clientConnection << " closed");
- return 1;
+ return true;
} else if (!Config.onoff.half_closed_clients) {
/* admin doesn't want to support half-closed client sockets */
debugs(33, 3, HERE << clientConnection << " aborted (half_closed_clients disabled)");
notifyAllContexts(0); // no specific error implies abort
- return 1;
+ return true;
}
}
- return 0;
+ return false;
}
void
ConnStateData::consumeInput(const size_t byteCount)
{
- assert(byteCount > 0 && byteCount <= in.buf.length());
- in.buf.consume(byteCount);
- debugs(33, 5, "in.buf has " << in.buf.length() << " unused bytes");
+ assert(byteCount > 0 && byteCount <= inBuf.length());
+ inBuf.consume(byteCount);
+ debugs(33, 5, "inBuf has " << inBuf.length() << " unused bytes");
}
void
// http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt
// detect and parse PROXY/2.0 protocol header
- if (in.buf.startsWith(Proxy2p0magic))
+ if (inBuf.startsWith(Proxy2p0magic))
return parseProxy2p0();
// detect and parse PROXY/1.0 protocol header
- if (in.buf.startsWith(Proxy1p0magic))
+ if (inBuf.startsWith(Proxy1p0magic))
return parseProxy1p0();
// detect and terminate other protocols
- if (in.buf.length() >= Proxy2p0magic.length()) {
+ if (inBuf.length() >= Proxy2p0magic.length()) {
// PROXY/1.0 magic is shorter, so we know that
// the input does not start with any PROXY magic
return proxyProtocolError("PROXY protocol error: invalid header");
bool
ConnStateData::parseProxy1p0()
{
- ::Parser::Tokenizer tok(in.buf);
+ ::Parser::Tokenizer tok(inBuf);
tok.skip(Proxy1p0magic);
// skip to first LF (assumes it is part of CRLF)
if (tok.prefix(line, lineContent, 107-Proxy1p0magic.length())) {
if (tok.skip('\n')) {
// found valid header
- in.buf = tok.remaining();
+ inBuf = tok.remaining();
needProxyProtocolHeader_ = false;
// reset the tokenizer to work on found line only.
tok.reset(line);
return false; // no LF yet
} else // protocol error only if there are more than 107 bytes prefix header
- return proxyProtocolError(in.buf.length() > 107? "PROXY/1.0 error: missing CRLF" : NULL);
+ return proxyProtocolError(inBuf.length() > 107? "PROXY/1.0 error: missing CRLF" : NULL);
static const SBuf unknown("UNKNOWN"), tcpName("TCP");
if (tok.skip(tcpName)) {
ConnStateData::parseProxy2p0()
{
static const SBuf::size_type prefixLen = Proxy2p0magic.length();
- if (in.buf.length() < prefixLen + 4)
+ if (inBuf.length() < prefixLen + 4)
return false; // need more bytes
- if ((in.buf[prefixLen] & 0xF0) != 0x20) // version == 2 is mandatory
+ if ((inBuf[prefixLen] & 0xF0) != 0x20) // version == 2 is mandatory
return proxyProtocolError("PROXY/2.0 error: invalid version");
- const char command = (in.buf[prefixLen] & 0x0F);
+ const char command = (inBuf[prefixLen] & 0x0F);
if ((command & 0xFE) != 0x00) // values other than 0x0-0x1 are invalid
return proxyProtocolError("PROXY/2.0 error: invalid command");
- const char family = (in.buf[prefixLen+1] & 0xF0) >>4;
+ const char family = (inBuf[prefixLen+1] & 0xF0) >>4;
if (family > 0x3) // values other than 0x0-0x3 are invalid
return proxyProtocolError("PROXY/2.0 error: invalid family");
- const char proto = (in.buf[prefixLen+1] & 0x0F);
+ const char proto = (inBuf[prefixLen+1] & 0x0F);
if (proto > 0x2) // values other than 0x0-0x2 are invalid
return proxyProtocolError("PROXY/2.0 error: invalid protocol type");
- const char *clen = in.buf.rawContent() + prefixLen + 2;
+ const char *clen = inBuf.rawContent() + prefixLen + 2;
uint16_t len;
memcpy(&len, clen, sizeof(len));
len = ntohs(len);
- if (in.buf.length() < prefixLen + 4 + len)
+ if (inBuf.length() < prefixLen + 4 + len)
return false; // need more bytes
- in.buf.consume(prefixLen + 4); // 4 being the extra bytes
- const SBuf extra = in.buf.consume(len);
+ inBuf.consume(prefixLen + 4); // 4 being the extra bytes
+ const SBuf extra = inBuf.consume(len);
needProxyProtocolHeader_ = false; // found successfully
// LOCAL connections do nothing with the extras
// Loop while we have read bytes that are not needed for producing the body
// On errors, bodyPipe may become nil, but readMore will be cleared
- while (!in.buf.isEmpty() && !bodyPipe && flags.readMore) {
+ while (!inBuf.isEmpty() && !bodyPipe && flags.readMore) {
/* Don't try to parse if the buffer is empty */
- if (in.buf.isEmpty())
+ if (inBuf.isEmpty())
break;
/* Limit the number of concurrent requests */
}
} else {
debugs(33, 5, clientConnection << ": not enough request data: " <<
- in.buf.length() << " < " << Config.maxRequestHeaderSize);
- Must(in.buf.length() < Config.maxRequestHeaderSize);
+ inBuf.length() << " < " << Config.maxRequestHeaderSize);
+ Must(inBuf.length() < Config.maxRequestHeaderSize);
break;
}
}
}
void
-ConnStateData::clientReadRequest(const CommIoCbParams &io)
+ConnStateData::afterClientRead()
{
- debugs(33,5, io.conn);
- Must(reading());
- reader = NULL;
-
- /* Bail out quickly on Comm::ERR_CLOSING - close handlers will tidy up */
- if (io.flag == Comm::ERR_CLOSING) {
- debugs(33,5, io.conn << " closing Bailout.");
- return;
- }
-
- assert(Comm::IsConnOpen(clientConnection));
- assert(io.conn->fd == clientConnection->fd);
-
- /*
- * Don't reset the timeout value here. The value should be
- * counting Config.Timeout.request and applies to the request
- * as a whole, not individual read() calls.
- * Plus, it breaks our lame *HalfClosed() detection
- */
-
- in.maybeMakeSpaceAvailable();
- CommIoCbParams rd(this); // will be expanded with ReadNow results
- rd.conn = io.conn;
- switch (Comm::ReadNow(rd, in.buf)) {
- case Comm::INPROGRESS:
- if (in.buf.isEmpty())
- debugs(33, 2, io.conn << ": no data to process, " << xstrerr(rd.xerrno));
- readSomeData();
- return;
-
- case Comm::OK:
- statCounter.client_http.kbytes_in += rd.size;
- if (!receivedFirstByte_)
- receivedFirstByte();
- // may comm_close or setReplyToError
- if (!handleReadData())
- return;
-
- /* Continue to process previously read data */
- break;
-
- case Comm::ENDFILE: // close detected by 0-byte read
- debugs(33, 5, io.conn << " closed?");
-
- if (connFinishedWithConn(rd.size)) {
- clientConnection->close();
- return;
- }
-
- /* It might be half-closed, we can't tell */
- fd_table[io.conn->fd].flags.socket_eof = true;
- commMarkHalfClosed(io.conn->fd);
- fd_note(io.conn->fd, "half-closed");
-
- /* There is one more close check at the end, to detect aborted
- * (partial) requests. At this point we can't tell if the request
- * is partial.
- */
-
- /* Continue to process previously read data */
- break;
-
- // case Comm::COMM_ERROR:
- default: // no other flags should ever occur
- debugs(33, 2, io.conn << ": got flag " << rd.flag << "; " << xstrerr(rd.xerrno));
- notifyAllContexts(rd.xerrno);
- io.conn->close();
- return;
- }
-
/* Process next request */
if (getConcurrentRequestCount() == 0)
- fd_note(io.fd, "Reading next request");
+ fd_note(clientConnection->fd, "Reading next request");
if (!clientParseRequests()) {
if (!isOpen())
* be if we have an incomplete request.
* XXX: This duplicates ClientSocketContext::keepaliveNextRequest
*/
- if (getConcurrentRequestCount() == 0 && commIsHalfClosed(io.fd)) {
- debugs(33, 5, HERE << io.conn << ": half-closed connection, no completed request parsed, connection closing.");
+ if (getConcurrentRequestCount() == 0 && commIsHalfClosed(clientConnection->fd)) {
+ debugs(33, 5, clientConnection << ": half-closed connection, no completed request parsed, connection closing.");
clientConnection->close();
return;
}
}
/**
- * called when new request body data has been buffered in in.buf
+ * called when new request body data has been buffered in inBuf
* may close the connection if we were closing and piped everything out
*
* \retval false called comm_close or setReplyToError (the caller should bail)
{
assert(bodyPipe != NULL);
- if (in.bodyParser) { // chunked encoding
+ if (bodyParser) { // chunked encoding
if (const err_type error = handleChunkedRequestBody()) {
abortChunkedRequestBody(error);
return false;
}
} else { // identity encoding
debugs(33,5, HERE << "handling plain request body for " << clientConnection);
- const size_t putSize = bodyPipe->putMoreData(in.buf.c_str(), in.buf.length());
+ const size_t putSize = bodyPipe->putMoreData(inBuf.c_str(), inBuf.length());
if (putSize > 0)
consumeInput(putSize);
err_type
ConnStateData::handleChunkedRequestBody()
{
- debugs(33, 7, "chunked from " << clientConnection << ": " << in.buf.length());
+ debugs(33, 7, "chunked from " << clientConnection << ": " << inBuf.length());
try { // the parser will throw on errors
- if (in.buf.isEmpty()) // nothing to do
+ if (inBuf.isEmpty()) // nothing to do
return ERR_NONE;
BodyPipeCheckout bpc(*bodyPipe);
- in.bodyParser->setPayloadBuffer(&bpc.buf);
- const bool parsed = in.bodyParser->parse(in.buf);
- in.buf = in.bodyParser->remaining(); // sync buffers
+ bodyParser->setPayloadBuffer(&bpc.buf);
+ const bool parsed = bodyParser->parse(inBuf);
+ inBuf = bodyParser->remaining(); // sync buffers
bpc.checkIn();
// dechunk then check: the size limit applies to _dechunked_ content
}
// if chunk parser needs data, then the body pipe must need it too
- Must(!in.bodyParser->needsMoreData() || bodyPipe->mayNeedMoreData());
+ Must(!bodyParser->needsMoreData() || bodyPipe->mayNeedMoreData());
// if parser needs more space and we can consume nothing, we will stall
- Must(!in.bodyParser->needsMoreSpace() || bodyPipe->buf().hasContent());
+ Must(!bodyParser->needsMoreSpace() || bodyPipe->buf().hasContent());
} catch (...) { // TODO: be more specific
debugs(33, 3, HERE << "malformed chunks" << bodyPipe->status());
return ERR_INVALID_REQ;
repContext->http->uri,
CachePeer,
repContext->http->request,
- in.buf, NULL);
+ inBuf, NULL);
context->pullData();
} else {
// close or otherwise we may get stuck as nobody will notice the error?
ConnStateData::ConnStateData(const MasterXaction::Pointer &xact) :
AsyncJob("ConnStateData"), // kids overwrite
+ Server(xact),
+ bodyParser(nullptr),
nrequests(0),
#if USE_OPENSSL
sslBumpMode(Ssl::bumpEnd),
signAlgorithm(Ssl::algSignTrusted),
#endif
stoppedSending_(NULL),
- stoppedReceiving_(NULL),
- receivedFirstByte_(false)
+ stoppedReceiving_(NULL)
{
flags.readMore = true; // kids may overwrite
flags.swanSang = false;
pinning.peer = NULL;
// store the details required for creating more MasterXaction objects as new requests come in
- clientConnection = xact->tcpClient;
- port = xact->squidPort;
- transferProtocol = port->transport; // default to the *_port protocol= setting. may change later.
log_addr = xact->tcpClient->remote;
log_addr.applyMask(Config.Addrs.client_netmask);
debugs(33, 2, HERE << "sslBump not needed for " << connState->clientConnection);
connState->sslBumpMode = Ssl::bumpNone;
}
- connState->fakeAConnectRequest("ssl-bump", connState->in.buf);
+ connState->fakeAConnectRequest("ssl-bump", connState->inBuf);
}
/** handle a new HTTPS connection */
// reset the current protocol to HTTP/1.1 (was "HTTPS" for the bumping process)
transferProtocol = Http::ProtocolVersion();
- // in.buf still has the "CONNECT ..." request data, reset it to SSL hello message
- in.buf.append(rbuf.content(), rbuf.contentSize());
+ // inBuf still has the "CONNECT ..." request data, reset it to SSL hello message
+ inBuf.append(rbuf.content(), rbuf.contentSize());
ClientSocketContext::Pointer context = getCurrentContext();
ClientHttpRequest *http = context->http;
tunnelStart(http);
retStr.append(connectHost);
retStr.append("\r\n\r\n");
retStr.append(payload);
- in.buf = retStr;
+ inBuf = retStr;
bool ret = handleReadData();
if (ret)
ret = clientParseRequests();
return clientConnection != NULL && (clientConnection->flags & (COMM_TRANSPARENT|COMM_INTERCEPTION));
}
-bool
-ConnStateData::reading() const
-{
- return reader != NULL;
-}
-
-void
-ConnStateData::stopReading()
-{
- if (reading()) {
- Comm::ReadCancel(clientConnection->fd, reader);
- reader = NULL;
- }
-}
-
BodyPipe::Pointer
ConnStateData::expectRequestBody(int64_t size)
{
return -1; // probably need to read more, but we cannot be sure
const int64_t needToProduce = bodyPipe->unproducedSize();
- const int64_t haveAvailable = static_cast<int64_t>(in.buf.length());
+ const int64_t haveAvailable = static_cast<int64_t>(inBuf.length());
if (needToProduce <= haveAvailable)
return 0; // we have read what we need (but are waiting for pipe space)
{
Must(bodyPipe != NULL);
debugs(33, 5, HERE << "start dechunking" << bodyPipe->status());
- assert(!in.bodyParser);
- in.bodyParser = new Http1::TeChunkedParser;
+ assert(!bodyParser);
+ bodyParser = new Http1::TeChunkedParser;
}
/// put parsed content into input buffer and clean up
}
}
- delete in.bodyParser;
- in.bodyParser = NULL;
-}
-
-ConnStateData::In::In() :
- bodyParser(NULL),
- buf()
-{}
-
-ConnStateData::In::~In()
-{
- delete bodyParser; // TODO: pool
+ delete bodyParser;
+ bodyParser = NULL;
}
void
--- /dev/null
+/*
+ * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
+ */
+
+#include "squid.h"
+#include "anyp/PortCfg.h"
+#include "comm.h"
+#include "comm/Read.h"
+#include "Debug.h"
+#include "fd.h"
+#include "fde.h"
+#include "MasterXaction.h"
+#include "servers/Server.h"
+#include "SquidConfig.h"
+#include "StatCounters.h"
+#include "tools.h"
+
+Server::Server(const MasterXaction::Pointer &xact) :
+ AsyncJob("::Server"), // kids overwrite
+ clientConnection(xact->tcpClient),
+ transferProtocol(xact->squidPort->transport),
+ port(xact->squidPort),
+ receivedFirstByte_(false)
+{}
+
+bool
+Server::doneAll() const
+{
+ // servers are not done while the connection is open
+ return !Comm::IsConnOpen(clientConnection) &&
+ BodyProducer::doneAll();
+}
+
+void
+Server::start()
+{
+ // TODO: shuffle activity from ConnStateData
+}
+
+void
+Server::swanSong()
+{
+ if (Comm::IsConnOpen(clientConnection))
+ clientConnection->close();
+
+ BodyProducer::swanSong();
+}
+
+void
+Server::stopReading()
+{
+ if (reading()) {
+ Comm::ReadCancel(clientConnection->fd, reader);
+ reader = NULL;
+ }
+}
+
+bool
+Server::maybeMakeSpaceAvailable()
+{
+ if (inBuf.spaceSize() < 2) {
+ const SBuf::size_type haveCapacity = inBuf.length() + inBuf.spaceSize();
+ if (haveCapacity >= Config.maxRequestBufferSize) {
+ debugs(33, 4, "request buffer full: client_request_buffer_max_size=" << Config.maxRequestBufferSize);
+ return false;
+ }
+ if (haveCapacity == 0) {
+ // haveCapacity is based on the SBuf visible window of the MemBlob buffer, which may fill up.
+ // at which point bump the buffer back to default. This allocates a new MemBlob with any un-parsed bytes.
+ inBuf.reserveCapacity(CLIENT_REQ_BUF_SZ);
+ } else {
+ const SBuf::size_type wantCapacity = min(static_cast<SBuf::size_type>(Config.maxRequestBufferSize), haveCapacity*2);
+ inBuf.reserveCapacity(wantCapacity);
+ }
+ debugs(33, 2, "growing request buffer: available=" << inBuf.spaceSize() << " used=" << inBuf.length());
+ }
+ return (inBuf.spaceSize() >= 2);
+}
+
+void
+Server::readSomeData()
+{
+ if (reading())
+ return;
+
+ debugs(33, 4, clientConnection << ": reading request...");
+
+ // we can only read if there is more than 1 byte of space free
+ if (Config.maxRequestBufferSize - inBuf.length() < 2)
+ return;
+
+ typedef CommCbMemFunT<Server, CommIoCbParams> Dialer;
+ reader = JobCallback(33, 5, Dialer, this, Server::doClientRead);
+ Comm::Read(clientConnection, reader);
+}
+
+void
+Server::doClientRead(const CommIoCbParams &io)
+{
+ debugs(33,5, io.conn);
+ Must(reading());
+ reader = NULL;
+
+ /* Bail out quickly on Comm::ERR_CLOSING - close handlers will tidy up */
+ if (io.flag == Comm::ERR_CLOSING) {
+ debugs(33,5, io.conn << " closing Bailout.");
+ return;
+ }
+
+ assert(Comm::IsConnOpen(clientConnection));
+ assert(io.conn->fd == clientConnection->fd);
+
+ /*
+ * Don't reset the timeout value here. The value should be
+ * counting Config.Timeout.request and applies to the request
+ * as a whole, not individual read() calls.
+ * Plus, it breaks our lame *HalfClosed() detection
+ */
+
+ maybeMakeSpaceAvailable();
+ CommIoCbParams rd(this); // will be expanded with ReadNow results
+ rd.conn = io.conn;
+ switch (Comm::ReadNow(rd, inBuf)) {
+ case Comm::INPROGRESS:
+
+ if (inBuf.isEmpty())
+ debugs(33, 2, io.conn << ": no data to process, " << xstrerr(rd.xerrno));
+ readSomeData();
+ return;
+
+ case Comm::OK:
+ statCounter.client_http.kbytes_in += rd.size;
+ if (!receivedFirstByte_)
+ receivedFirstByte();
+ // may comm_close or setReplyToError
+ if (!handleReadData())
+ return;
+
+ /* Continue to process previously read data */
+ break;
+
+ case Comm::ENDFILE: // close detected by 0-byte read
+ debugs(33, 5, io.conn << " closed?");
+
+ if (connFinishedWithConn(rd.size)) {
+ clientConnection->close();
+ return;
+ }
+
+ /* It might be half-closed, we can't tell */
+ fd_table[io.conn->fd].flags.socket_eof = true;
+ commMarkHalfClosed(io.conn->fd);
+ fd_note(io.conn->fd, "half-closed");
+
+ /* There is one more close check at the end, to detect aborted
+ * (partial) requests. At this point we can't tell if the request
+ * is partial.
+ */
+
+ /* Continue to process previously read data */
+ break;
+
+ // case Comm::COMM_ERROR:
+ default: // no other flags should ever occur
+ debugs(33, 2, io.conn << ": got flag " << rd.flag << "; " << xstrerr(rd.xerrno));
+ notifyAllContexts(rd.xerrno);
+ io.conn->close();
+ return;
+ }
+
+ afterClientRead();
+}
+
+void
+Server::clientWriteDone(const CommIoCbParams &io)
+{
+ debugs(33,5, io.conn);
+ Must(writer != NULL);
+ writer = NULL;
+
+ /* Bail out quickly on Comm::ERR_CLOSING - close handlers will tidy up */
+ if (io.flag == Comm::ERR_CLOSING) {
+ debugs(33,5, io.conn << " closing Bailout.");
+ return;
+ }
+
+ assert(Comm::IsConnOpen(clientConnection));
+ assert(io.conn->fd == clientConnection->fd);
+
+ writeSomeData(); // maybe schedules another write
+}