* Support the new {send,read}_data_no_buffer() protocol hooks to avoid

unnecessary data copies and waste of memory.
* Changed the storage backend to ring_buffer.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@25300 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Ingo Weinhold
2008-05-03 01:14:44 +00:00
parent cdc00dadfc
commit ea035707b3
5 changed files with 368 additions and 401 deletions
@@ -414,11 +414,12 @@ UnixEndpoint::Accept(net_socket **_acceptedSocket)
} }
status_t ssize_t
UnixEndpoint::Send(net_buffer *buffer) UnixEndpoint::Send(const iovec *vecs, size_t vecCount,
ancillary_data_container *ancillaryData)
{ {
TRACE("[%ld] %p->UnixEndpoint::Send(%p)\n", find_thread(NULL), this, TRACE("[%ld] %p->UnixEndpoint::Send(%p, %ld, %p)\n", find_thread(NULL),
buffer); this, vecs, vecCount, ancillaryData);
bigtime_t timeout = absolute_timeout(socket->send.timeout); bigtime_t timeout = absolute_timeout(socket->send.timeout);
if (gStackModule->is_restarted_syscall()) if (gStackModule->is_restarted_syscall())
@@ -445,7 +446,7 @@ UnixEndpoint::Send(net_buffer *buffer)
locker.Unlock(); locker.Unlock();
peerLocker.Unlock(); peerLocker.Unlock();
error = peerFifo->Write(buffer, timeout); ssize_t result = peerFifo->Write(vecs, vecCount, ancillaryData, timeout);
// Notify select()ing readers, if we successfully wrote anything. // Notify select()ing readers, if we successfully wrote anything.
size_t readable = peerFifo->Readable(); size_t readable = peerFifo->Readable();
@@ -471,17 +472,17 @@ UnixEndpoint::Send(net_buffer *buffer)
if (notifyWrite) if (notifyWrite)
gSocketModule->notify(socket, B_SELECT_WRITE, writable); gSocketModule->notify(socket, B_SELECT_WRITE, writable);
switch (error) { switch (result) {
case UNIX_FIFO_SHUTDOWN: case UNIX_FIFO_SHUTDOWN:
if (fPeerEndpoint == peerEndpoint if (fPeerEndpoint == peerEndpoint
&& fState == UNIX_ENDPOINT_CONNECTED) { && fState == UNIX_ENDPOINT_CONNECTED) {
// Orderly write shutdown on our side. // Orderly write shutdown on our side.
// Note: Linux and Solaris also send a SIGPIPE, but according // Note: Linux and Solaris also send a SIGPIPE, but according
// the send() specification that shouldn't be done. // the send() specification that shouldn't be done.
error = EPIPE; result = EPIPE;
} else { } else {
// The FD has been closed. // The FD has been closed.
error = EBADF; result = EBADF;
} }
break; break;
case EPIPE: case EPIPE:
@@ -493,19 +494,21 @@ UnixEndpoint::Send(net_buffer *buffer)
case B_TIMED_OUT: case B_TIMED_OUT:
// Translate non-blocking timeouts to the correct error code. // Translate non-blocking timeouts to the correct error code.
if (timeout == 0) if (timeout == 0)
error = B_WOULD_BLOCK; result = B_WOULD_BLOCK;
break; break;
} }
RETURN_ERROR(error); RETURN_ERROR(result);
} }
status_t ssize_t
UnixEndpoint::Receive(size_t numBytes, uint32 flags, net_buffer **_buffer) UnixEndpoint::Receive(const iovec *vecs, size_t vecCount,
ancillary_data_container **_ancillaryData, struct sockaddr *_address,
socklen_t *_addressLength)
{ {
TRACE("[%ld] %p->UnixEndpoint::Receive(%ld, 0x%lx)\n", find_thread(NULL), TRACE("[%ld] %p->UnixEndpoint::Receive(%p, %ld)\n", find_thread(NULL),
this, numBytes, flags); this, vecs, vecCount);
bigtime_t timeout = absolute_timeout(socket->receive.timeout); bigtime_t timeout = absolute_timeout(socket->receive.timeout);
if (gStackModule->is_restarted_syscall()) if (gStackModule->is_restarted_syscall())
@@ -523,6 +526,14 @@ UnixEndpoint::Receive(size_t numBytes, uint32 flags, net_buffer **_buffer)
UnixEndpoint* peerEndpoint = fPeerEndpoint; UnixEndpoint* peerEndpoint = fPeerEndpoint;
Reference<UnixEndpoint> peerReference(peerEndpoint); Reference<UnixEndpoint> peerReference(peerEndpoint);
// Copy the peer address upfront. This way, if we read something, we don't
// get into a potential race with Close().
if (_address != NULL) {
socklen_t addrLen = min_c(*_addressLength, socket->peer.ss_len);
memcpy(_address, &socket->peer, addrLen);
*_addressLength = addrLen;
}
// lock our FIFO // lock our FIFO
UnixFifo* fifo = fReceiveFifo; UnixFifo* fifo = fReceiveFifo;
Reference<UnixFifo> _(fifo); Reference<UnixFifo> _(fifo);
@@ -531,17 +542,17 @@ UnixEndpoint::Receive(size_t numBytes, uint32 flags, net_buffer **_buffer)
// unlock endpoint // unlock endpoint
locker.Unlock(); locker.Unlock();
status_t error = fifo->Read(numBytes, timeout, _buffer); ssize_t result = fifo->Read(vecs, vecCount, _ancillaryData, timeout);
// Notify select()ing writers, if we successfully read anything. // Notify select()ing writers, if we successfully read anything.
size_t writable = fifo->Writable(); size_t writable = fifo->Writable();
bool notifyWrite = (error == B_OK && writable > 0 bool notifyWrite = (result >= 0 && writable > 0
&& !fifo->IsWriteShutdown()); && !fifo->IsWriteShutdown());
// Notify select()ing readers, if we failed to read anything and there's // Notify select()ing readers, if we failed to read anything and there's
// still something left to read. // still something left to read.
size_t readable = fifo->Readable(); size_t readable = fifo->Readable();
bool notifyRead = (error != B_OK && readable > 0 bool notifyRead = (result < 0 && readable > 0
&& !fifo->IsReadShutdown()); && !fifo->IsReadShutdown());
// re-lock our endpoint (unlock FIFO to respect locking order) // re-lock our endpoint (unlock FIFO to respect locking order)
@@ -558,12 +569,12 @@ UnixEndpoint::Receive(size_t numBytes, uint32 flags, net_buffer **_buffer)
if (peerLocked && notifyWrite) if (peerLocked && notifyWrite)
gSocketModule->notify(peerEndpoint->socket, B_SELECT_WRITE, writable); gSocketModule->notify(peerEndpoint->socket, B_SELECT_WRITE, writable);
switch (error) { switch (result) {
case UNIX_FIFO_SHUTDOWN: case UNIX_FIFO_SHUTDOWN:
// Either our socket was closed or read shutdown. // Either our socket was closed or read shutdown.
if (fState == UNIX_ENDPOINT_CLOSED) { if (fState == UNIX_ENDPOINT_CLOSED) {
// The FD has been closed. // The FD has been closed.
error = EBADF; result = EBADF;
} else { } else {
// if (fReceiveFifo == fifo) { // if (fReceiveFifo == fifo) {
// Orderly shutdown or the peer closed the connection. // Orderly shutdown or the peer closed the connection.
@@ -571,18 +582,17 @@ UnixEndpoint::Receive(size_t numBytes, uint32 flags, net_buffer **_buffer)
// Weird case: Peer closed connection and we are already // Weird case: Peer closed connection and we are already
// reconnected (or listening). // reconnected (or listening).
// } // }
error = B_OK; result = 0;
*_buffer = NULL;
} }
break; break;
case B_TIMED_OUT: case B_TIMED_OUT:
// translate non-blocking timeouts to the correct error code // translate non-blocking timeouts to the correct error code
if (timeout == 0) if (timeout == 0)
error = B_WOULD_BLOCK; result = B_WOULD_BLOCK;
break; break;
} }
RETURN_ERROR(error); RETURN_ERROR(result);
} }
@@ -624,7 +634,7 @@ UnixEndpoint::Receivable()
} }
void status_t
UnixEndpoint::SetReceiveBufferSize(size_t size) UnixEndpoint::SetReceiveBufferSize(size_t size)
{ {
TRACE("[%ld] %p->UnixEndpoint::SetReceiveBufferSize(%lu)\n", TRACE("[%ld] %p->UnixEndpoint::SetReceiveBufferSize(%lu)\n",
@@ -632,11 +642,11 @@ UnixEndpoint::SetReceiveBufferSize(size_t size)
UnixEndpointLocker locker(this); UnixEndpointLocker locker(this);
if (fState != UNIX_ENDPOINT_CONNECTED) if (fReceiveFifo == NULL)
return; return B_BAD_VALUE;
UnixFifoLocker fifoLocker(fReceiveFifo); UnixFifoLocker fifoLocker(fReceiveFifo);
fReceiveFifo->SetBufferCapacity(size); return fReceiveFifo->SetBufferCapacity(size);
} }
@@ -66,13 +66,16 @@ public:
status_t Connect(const struct sockaddr *address); status_t Connect(const struct sockaddr *address);
status_t Accept(net_socket **_acceptedSocket); status_t Accept(net_socket **_acceptedSocket);
status_t Send(net_buffer *buffer); ssize_t Send(const iovec *vecs, size_t vecCount,
status_t Receive(size_t numBytes, uint32 flags, net_buffer **_buffer); ancillary_data_container *ancillaryData);
ssize_t Receive(const iovec *vecs, size_t vecCount,
ancillary_data_container **_ancillaryData, struct sockaddr *_address,
socklen_t *_addressLength);
ssize_t Sendable(); ssize_t Sendable();
ssize_t Receivable(); ssize_t Receivable();
void SetReceiveBufferSize(size_t size); status_t SetReceiveBufferSize(size_t size);
status_t Shutdown(int direction); status_t Shutdown(int direction);
@@ -5,8 +5,13 @@
#include "UnixFifo.h" #include "UnixFifo.h"
#include <new>
#include <AutoDeleter.h> #include <AutoDeleter.h>
#include <net_stack.h>
#include <util/ring_buffer.h>
#include "unix.h" #include "unix.h"
@@ -15,314 +20,242 @@
#include "UnixDebug.h" #include "UnixDebug.h"
#if TRACE_BUFFER_QUEUE // #pragma mark - UnixRequest
# define TRACEBQ(x...) ktrace_printf(x)
# define TRACEBQ_ONLY(x) x
#else UnixRequest::UnixRequest(const iovec* vecs, size_t count,
# define TRACEBQ(x...) do {} while (false) ancillary_data_container* ancillaryData)
# define TRACEBQ_ONLY(x) :
#endif fVecs(vecs),
fVecCount(count),
fAncillaryData(ancillaryData),
fTotalSize(0),
fBytesTransferred(0),
fVecIndex(0),
fVecOffset(0)
{
for (size_t i = 0; i < fVecCount; i++)
fTotalSize += fVecs[i].iov_len;
}
void
UnixRequest::AddBytesTransferred(size_t size)
{
fBytesTransferred += size;
// also adjust the current iovec index/offset
while (fVecIndex < fVecCount
&& fVecs[fVecIndex].iov_len - fVecOffset <= size) {
size -= fVecs[fVecIndex].iov_len - fVecOffset;
fVecIndex++;
fVecOffset = 0;
}
if (fVecIndex < fVecCount)
fVecOffset += size;
}
bool
UnixRequest::GetCurrentChunk(void*& data, size_t& size)
{
while (fVecIndex < fVecCount
&& fVecOffset >= fVecs[fVecIndex].iov_len) {
fVecIndex++;
fVecOffset = 0;
}
if (fVecIndex >= fVecCount)
return false;
data = (uint8*)fVecs[fVecIndex].iov_base + fVecOffset;
size = fVecs[fVecIndex].iov_len - fVecOffset;
return true;
}
void
UnixRequest::SetAncillaryData(ancillary_data_container* data)
{
fAncillaryData = data;
}
void
UnixRequest::AddAncillaryData(ancillary_data_container* data)
{
if (fAncillaryData != NULL) {
gStackModule->move_ancillary_data(data, fAncillaryData);
gStackModule->delete_ancillary_data_container(data);
} else
fAncillaryData = data;
}
// #pragma mark - UnixBufferQueue
UnixBufferQueue::UnixBufferQueue(size_t capacity) UnixBufferQueue::UnixBufferQueue(size_t capacity)
: :
fSize(0), fBuffer(NULL),
fCapacity(capacity) fCapacity(capacity)
#if TRACE_BUFFER_QUEUE
, fWritten(0)
, fRead(0)
#endif
{ {
TRACEBQ_ONLY(
fParanoiaCheckBuffer = (uint8*)malloc(UNIX_FIFO_MAXIMAL_CAPACITY);
fParanoiaCheckBuffer2 = (uint8*)malloc(UNIX_FIFO_MAXIMAL_CAPACITY);
)
} }
UnixBufferQueue::~UnixBufferQueue() UnixBufferQueue::~UnixBufferQueue()
{ {
while (net_buffer* buffer = fBuffers.RemoveHead()) while (AncillaryDataEntry* entry = fAncillaryData.RemoveHead()) {
gBufferModule->free(buffer); gStackModule->delete_ancillary_data_container(entry->data);
delete entry;
}
TRACEBQ_ONLY( delete_ring_buffer(fBuffer);
free(fParanoiaCheckBuffer);
free(fParanoiaCheckBuffer2);
)
} }
status_t status_t
UnixBufferQueue::Read(size_t size, net_buffer** _buffer) UnixBufferQueue::Init()
{ {
if (size > fSize) fBuffer = create_ring_buffer(fCapacity);
size = fSize; if (fBuffer == NULL)
return B_NO_MEMORY;
TRACEBQ("unix: UnixBufferQueue::Read(%lu): fSize: %lu, fRead: %lld, " return B_OK;
"fWritten: %lld", size, fSize, fRead, fWritten);
TRACEBQ_ONLY(
MethodDeleter<UnixBufferQueue> _(this, &UnixBufferQueue::PostReadWrite);
)
if (size == 0)
return B_BAD_VALUE;
TRACEBQ_ONLY(
if (fParanoiaCheckBuffer) {
size_t bufferSize = 0;
for (BufferList::Iterator it = fBuffers.GetIterator();
net_buffer* buffer = it.Next();) {
size_t toWrite = min_c(buffer->size, size - bufferSize);
if (toWrite == 0)
break;
gBufferModule->read(buffer, 0,
fParanoiaCheckBuffer + bufferSize, toWrite);
bufferSize += toWrite;
}
}
*_buffer = NULL;
)
// If the first buffer has the right size or is smaller, we can just
// dequeue it.
net_buffer* buffer = fBuffers.Head();
if (buffer->size <= size) {
fBuffers.RemoveHead();
fSize -= buffer->size;
TRACEBQ_ONLY(fRead += buffer->size);
*_buffer = buffer;
if (buffer->size == size)
{
TRACEBQ("unix: read full buffer %p (%lu)", buffer, buffer->size);
TRACEBQ_ONLY(ParanoiaReadCheck(*_buffer));
return B_OK;
} }
// buffer is too small
size_t bytesLeft = size - buffer->size; size_t
TRACEBQ("unix: read short buffer %p (%lu/%lu)", buffer, size, buffer->size); UnixBufferQueue::Readable() const
// Append from the following buffers, until we've read as much as we're
// supposed to.
while (bytesLeft > 0) {
net_buffer* nextBuffer = fBuffers.Head();
size_t toCopy = min_c(bytesLeft, nextBuffer->size);
TRACEBQ("unix: read next buffer %p (%lu/%lu)", nextBuffer, bytesLeft, nextBuffer->size);
#if 0
if (gBufferModule->append_cloned(buffer, nextBuffer, 0, toCopy)
!= B_OK) {
// Too bad, but we've got some data, so we don't fail.
TRACEBQ_ONLY(ParanoiaReadCheck(*_buffer));
return B_OK;
}
#endif
// TODO: Temporary work-around for the append_cloned() call above, which
// doesn't seem to work right. Or maybe that's just in combination with the
// remove_header() below.
{ {
void* tmpBuffer = malloc(toCopy); return ring_buffer_readable(fBuffer);
if (tmpBuffer == NULL)
return B_OK;
MemoryDeleter tmpBufferDeleter(tmpBuffer);
size_t offset = buffer->size;
if (gBufferModule->read(nextBuffer, 0, tmpBuffer, toCopy) != B_OK
|| gBufferModule->append_size(buffer, toCopy, NULL) != B_OK) {
return B_OK;
}
if (gBufferModule->write(buffer, offset, tmpBuffer, toCopy) != B_OK) {
gBufferModule->remove_trailer(buffer, toCopy);
return B_OK;
}
} }
// transfer the ancillary data
gBufferModule->transfer_ancillary_data(nextBuffer, buffer);
if (nextBuffer->size > toCopy) { size_t
// remove the part we've copied UnixBufferQueue::Writable() const
//gBufferModule->read(nextBuffer, toCopy, fParanoiaCheckBuffer, {
//nextBuffer->size - toCopy); return ring_buffer_writable(fBuffer);
gBufferModule->remove_header(nextBuffer, toCopy); }
//TRACEBQ_ONLY(ParanoiaReadCheck(nextBuffer));
} else {
// get rid of the buffer completely status_t
fBuffers.RemoveHead(); UnixBufferQueue::Read(UnixRequest& request)
gBufferModule->free(nextBuffer); {
bool user = gStackModule->is_syscall();
size_t readable = Readable();
void* data;
size_t size;
while (readable > 0 && request.GetCurrentChunk(data, size)) {
if (size > readable)
size = readable;
ssize_t bytesRead;
if (user)
bytesRead = ring_buffer_user_read(fBuffer, (uint8*)data, size);
else
bytesRead = ring_buffer_read(fBuffer, (uint8*)data, size);
if (bytesRead < 0)
return bytesRead;
if (bytesRead == 0)
return B_ERROR;
// Adjust ancillary data entry offsets, respectively attach the ones
// that belong to the read data to the request.
if (AncillaryDataEntry* entry = fAncillaryData.Head()) {
size_t offsetDelta = bytesRead;
while (entry != NULL && offsetDelta > entry->offset) {
// entry data have been read -- add ancillary data to request
fAncillaryData.RemoveHead();
offsetDelta -= entry->offset;
request.AddAncillaryData(entry->data);
delete entry;
entry = fAncillaryData.Head();
} }
bytesLeft -= toCopy; if (entry != NULL)
fSize -= toCopy; entry->offset -= offsetDelta;
TRACEBQ_ONLY(fRead += toCopy);
} }
TRACEBQ_ONLY(ParanoiaReadCheck(*_buffer)); request.AddBytesTransferred(bytesRead);
return B_OK; readable -= bytesRead;
} }
// buffer is too big
// Create a new buffer, and copy into it, as much as we need.
net_buffer* newBuffer = gBufferModule->create(256);
if (newBuffer == NULL)
return ENOBUFS;
status_t error = gBufferModule->append_cloned(newBuffer, buffer, 0, size);
if (error != B_OK) {
gBufferModule->free(newBuffer);
return error;
}
// transfer the ancillary data
gBufferModule->transfer_ancillary_data(buffer, newBuffer);
// remove the part we've copied
TRACEBQ("unix: read long buffer %p (%lu/%lu)", buffer, size, buffer->size);
gBufferModule->remove_header(buffer, size);
fSize -= size;
TRACEBQ_ONLY(fRead += size);
*_buffer = newBuffer;
TRACEBQ_ONLY(ParanoiaReadCheck(*_buffer));
return B_OK; return B_OK;
} }
status_t status_t
UnixBufferQueue::Write(net_buffer* buffer, size_t maxSize) UnixBufferQueue::Write(UnixRequest& request)
{ {
TRACEBQ("unix: UnixBufferQueue::Write(%lu/%lu): fSize: %lu, fRead: %lld, " bool user = gStackModule->is_syscall();
"fWritten: %lld", buffer->size, maxSize, fSize, fRead, fWritten);
TRACEBQ_ONLY( size_t writable = Writable();
MethodDeleter<UnixBufferQueue> _(this, &UnixBufferQueue::PostReadWrite); void* data;
) size_t size;
maxSize = min_c(buffer->size, maxSize); // If the request has ancillary data create an entry first.
if (maxSize > Writable()) AncillaryDataEntry* ancillaryEntry = NULL;
RETURN_ERROR(ENOBUFS); ObjectDeleter<AncillaryDataEntry> ancillaryEntryDeleter;
if (writable > 0 && request.AncillaryData() != NULL) {
ancillaryEntry = new(std::nothrow) AncillaryDataEntry;
if (ancillaryEntry == NULL)
return B_NO_MEMORY;
// If we shall write the complete buffer, things are easy. ancillaryEntryDeleter.SetTo(ancillaryEntry);
if (maxSize == buffer->size) { ancillaryEntry->data = request.AncillaryData();
fBuffers.Add(buffer); ancillaryEntry->offset = Readable();
fSize += buffer->size;
TRACEBQ_ONLY(fWritten += buffer->size);
return B_OK; // The offsets are relative to the previous entry.
AncillaryDataList::Iterator it = fAncillaryData.GetIterator();
while (AncillaryDataEntry* entry = it.Next())
ancillaryEntry->offset -= entry->offset;
// TODO: This is inefficient when the list is long. Rather also
// store and maintain the absolute offset of the last queued entry.
} }
// We shall write only a partial buffer. We need to create a new one and // write as much as we can
// cut of the head of the old one. while (writable > 0 && request.GetCurrentChunk(data, size)) {
// TODO: This implementation obviously sucks, but we can't use the split method, if (size > writable)
// since it would split off the wrong buffer. The socket module requires us size = writable;
// to cut off the head of the given one.
// create a temporary buffer ssize_t bytesWritten;
void* tmpBuffer = malloc(maxSize); if (user)
if (tmpBuffer == NULL) bytesWritten = ring_buffer_user_write(fBuffer, (uint8*)data, size);
return B_OK; else
MemoryDeleter tmpBufferDeleter(tmpBuffer); bytesWritten = ring_buffer_write(fBuffer, (uint8*)data, size);
// read the data to append into the temporary buffer if (bytesWritten < 0)
status_t error = gBufferModule->read(buffer, 0, tmpBuffer, maxSize); return bytesWritten;
if (error != B_OK) if (bytesWritten == 0)
return error; return B_ERROR;
// create the new buffer and append the data if (ancillaryEntry != NULL) {
net_buffer* newBuffer = gBufferModule->create(256); fAncillaryData.Add(ancillaryEntry);
if (newBuffer == NULL) ancillaryEntryDeleter.Detach();
return ENOBUFS; request.SetAncillaryData(NULL);
ancillaryEntry = NULL;
}
error = gBufferModule->append(newBuffer, tmpBuffer, maxSize); request.AddBytesTransferred(bytesWritten);
writable -= bytesWritten;
// remove the header from the old buffer
if (error == B_OK)
error = gBufferModule->remove_header(buffer, maxSize);
if (error != B_OK) {
gBufferModule->free(newBuffer);
return error;
} }
// transfer the ancillary data
gBufferModule->transfer_ancillary_data(buffer, newBuffer);
// Everything went fine. Append the new buffer.
fBuffers.Add(newBuffer);
fSize += newBuffer->size;
TRACEBQ_ONLY(fWritten += newBuffer->size);
return B_OK; return B_OK;
} }
void status_t
UnixBufferQueue::SetCapacity(size_t capacity) UnixBufferQueue::SetCapacity(size_t capacity)
{ {
fCapacity = capacity; // TODO:...
return B_ERROR;
} }
#if TRACE_BUFFER_QUEUE
void
UnixBufferQueue::ParanoiaReadCheck(net_buffer* buffer)
{
if (!buffer || !fParanoiaCheckBuffer || !fParanoiaCheckBuffer2)
return;
gBufferModule->read(buffer, 0, fParanoiaCheckBuffer2, buffer->size);
if (memcmp(fParanoiaCheckBuffer, fParanoiaCheckBuffer2, buffer->size)
!= 0) {
// find offset of first difference
size_t i = 0;
for (; i < buffer->size; i++) {
if (fParanoiaCheckBuffer[i] != fParanoiaCheckBuffer2[i])
break;
}
panic("unix: UnixBufferQueue::ParanoiaReadCheck(): incorrect read! "
"offset of first difference: %lu", i);
}
}
void
UnixBufferQueue::PostReadWrite()
{
TRACEBQ("unix: post read/write: fSize: %lu, fRead: %lld, fWritten: %lld",
fSize, fRead, fWritten);
if (fWritten - fRead != fSize) {
panic("UnixBufferQueue::PostReadWrite(): fSize: %lu, fRead: %lld, "
"fWritten: %lld", fSize, fRead, fWritten);
}
// check buffer size sum
size_t bufferSize = 0;
for (BufferList::Iterator it = fBuffers.GetIterator();
net_buffer* buffer = it.Next();) {
bufferSize += buffer->size;
}
if (bufferSize != fSize) {
panic("UnixBufferQueue::PostReadWrite(): fSize: %lu, bufferSize: %lu",
fSize, bufferSize);
}
}
#endif // TRACE_BUFFER_QUEUE
// #pragma mark - // #pragma mark -
@@ -351,7 +284,7 @@ UnixFifo::~UnixFifo()
status_t status_t
UnixFifo::Init() UnixFifo::Init()
{ {
return B_OK; return fBuffer.Init();
} }
@@ -371,24 +304,25 @@ UnixFifo::Shutdown(uint32 shutdown)
} }
status_t ssize_t
UnixFifo::Read(size_t numBytes, bigtime_t timeout, net_buffer** _buffer) UnixFifo::Read(const iovec* vecs, size_t vecCount,
ancillary_data_container** _ancillaryData, bigtime_t timeout)
{ {
TRACE("[%ld] %p->UnixFifo::Read(%lu, %lld)\n", find_thread(NULL), this, TRACE("[%ld] %p->UnixFifo::Read(%p, %ld, %lld)\n", find_thread(NULL),
numBytes, timeout); this, vecs, vecCount, timeout);
if (IsReadShutdown()) if (IsReadShutdown())
return UNIX_FIFO_SHUTDOWN; return UNIX_FIFO_SHUTDOWN;
Request request(numBytes); UnixRequest request(vecs, vecCount, NULL);
fReaders.Add(&request); fReaders.Add(&request);
fReadRequested += request.size; fReadRequested += request.TotalSize();
status_t error = _Read(request, numBytes, timeout, _buffer); status_t error = _Read(request, timeout);
bool firstInQueue = fReaders.Head() == &request; bool firstInQueue = fReaders.Head() == &request;
fReaders.Remove(&request); fReaders.Remove(&request);
fReadRequested -= request.size; fReadRequested -= request.TotalSize();
if (firstInQueue && !fReaders.IsEmpty() && fBuffer.Readable() > 0 if (firstInQueue && !fReaders.IsEmpty() && fBuffer.Readable() > 0
&& !IsReadShutdown()) { && !IsReadShutdown()) {
@@ -397,21 +331,30 @@ UnixFifo::Read(size_t numBytes, bigtime_t timeout, net_buffer** _buffer)
fReadCondition.NotifyAll(); fReadCondition.NotifyAll();
} }
if (error == B_OK && *_buffer != NULL && (*_buffer)->size > 0 if (request.BytesTransferred() > 0 && !fWriters.IsEmpty()
&& !fWriters.IsEmpty() && !IsWriteShutdown()) { && !IsWriteShutdown()) {
// We read something and there are writers. Notify them // We read something and there are writers. Notify them
fWriteCondition.NotifyAll(); fWriteCondition.NotifyAll();
} }
*_ancillaryData = request.AncillaryData();
if (request.BytesTransferred() > 0) {
if (request.BytesTransferred() > SSIZE_MAX)
RETURN_ERROR(SSIZE_MAX);
RETURN_ERROR((ssize_t)request.BytesTransferred());
}
RETURN_ERROR(error); RETURN_ERROR(error);
} }
status_t ssize_t
UnixFifo::Write(net_buffer* buffer, bigtime_t timeout) UnixFifo::Write(const iovec* vecs, size_t vecCount,
ancillary_data_container* ancillaryData, bigtime_t timeout)
{ {
TRACE("[%ld] %p->UnixFifo::Write(%p (%lu), %lld)\n", find_thread(NULL), TRACE("[%ld] %p->UnixFifo::Write(%p, %ld, %p, %lld)\n", find_thread(NULL),
this, buffer, buffer->size, timeout); this, vecs, vecCount, ancillaryData, timeout);
if (IsWriteShutdown()) if (IsWriteShutdown())
return UNIX_FIFO_SHUTDOWN; return UNIX_FIFO_SHUTDOWN;
@@ -419,16 +362,15 @@ UnixFifo::Write(net_buffer* buffer, bigtime_t timeout)
if (IsReadShutdown()) if (IsReadShutdown())
return EPIPE; return EPIPE;
Request request(buffer->size); UnixRequest request(vecs, vecCount, ancillaryData);
fWriters.Add(&request); fWriters.Add(&request);
fWriteRequested += request.size; fWriteRequested += request.TotalSize();
size_t bytesWritten = 0;
status_t error = _Write(request, buffer, timeout, bytesWritten); status_t error = _Write(request, timeout);
bool firstInQueue = fWriters.Head() == &request; bool firstInQueue = fWriters.Head() == &request;
fWriters.Remove(&request); fWriters.Remove(&request);
fWriteRequested -= request.size; fWriteRequested -= request.TotalSize();
if (firstInQueue && !fWriters.IsEmpty() && fBuffer.Writable() > 0 if (firstInQueue && !fWriters.IsEmpty() && fBuffer.Writable() > 0
&& !IsWriteShutdown()) { && !IsWriteShutdown()) {
@@ -437,12 +379,18 @@ UnixFifo::Write(net_buffer* buffer, bigtime_t timeout)
fWriteCondition.NotifyAll(); fWriteCondition.NotifyAll();
} }
if (bytesWritten > 0 && request.size > 0 && !fReaders.IsEmpty() if (request.BytesTransferred() > 0 && !fReaders.IsEmpty()
&& !IsReadShutdown()) { && !IsReadShutdown()) {
// We've written something and there are readers. Notify them. // We've written something and there are readers. Notify them.
fReadCondition.NotifyAll(); fReadCondition.NotifyAll();
} }
if (request.BytesTransferred() > 0) {
if (request.BytesTransferred() > SSIZE_MAX)
RETURN_ERROR(SSIZE_MAX);
RETURN_ERROR((ssize_t)request.BytesTransferred());
}
RETURN_ERROR(error); RETURN_ERROR(error);
} }
@@ -463,7 +411,7 @@ UnixFifo::Writable() const
} }
void status_t
UnixFifo::SetBufferCapacity(size_t capacity) UnixFifo::SetBufferCapacity(size_t capacity)
{ {
// check against allowed minimal/maximal value // check against allowed minimal/maximal value
@@ -474,20 +422,23 @@ UnixFifo::SetBufferCapacity(size_t capacity)
size_t oldCapacity = fBuffer.Capacity(); size_t oldCapacity = fBuffer.Capacity();
if (capacity == oldCapacity) if (capacity == oldCapacity)
return; return B_OK;
// set capacity // set capacity
fBuffer.SetCapacity(capacity); status_t error = fBuffer.SetCapacity(capacity);
if (error != B_OK)
return error;
// wake up waiting writers, if the capacity increased // wake up waiting writers, if the capacity increased
if (!fWriters.IsEmpty() && !IsWriteShutdown()) if (!fWriters.IsEmpty() && !IsWriteShutdown())
fWriteCondition.NotifyAll(); fWriteCondition.NotifyAll();
return B_OK;
} }
status_t status_t
UnixFifo::_Read(Request& request, size_t numBytes, bigtime_t timeout, UnixFifo::_Read(UnixRequest& request, bigtime_t timeout)
net_buffer** _buffer)
{ {
// wait for the request to reach the front of the queue // wait for the request to reach the front of the queue
if (fReaders.Head() != &request && timeout == 0) if (fReaders.Head() != &request && timeout == 0)
@@ -509,10 +460,8 @@ UnixFifo::_Read(Request& request, size_t numBytes, bigtime_t timeout,
return UNIX_FIFO_SHUTDOWN; return UNIX_FIFO_SHUTDOWN;
if (fBuffer.Readable() == 0) { if (fBuffer.Readable() == 0) {
if (IsWriteShutdown()) { if (IsWriteShutdown())
*_buffer = NULL; RETURN_ERROR(0);
RETURN_ERROR(B_OK);
}
if (timeout == 0) if (timeout == 0)
RETURN_ERROR(B_WOULD_BLOCK); RETURN_ERROR(B_WOULD_BLOCK);
@@ -536,21 +485,18 @@ UnixFifo::_Read(Request& request, size_t numBytes, bigtime_t timeout,
if (IsReadShutdown()) if (IsReadShutdown())
return UNIX_FIFO_SHUTDOWN; return UNIX_FIFO_SHUTDOWN;
if (fBuffer.Readable() == 0 && IsWriteShutdown()) { if (fBuffer.Readable() == 0 && IsWriteShutdown())
*_buffer = NULL; RETURN_ERROR(0);
RETURN_ERROR(B_OK);
}
RETURN_ERROR(fBuffer.Read(numBytes, _buffer)); RETURN_ERROR(fBuffer.Read(request));
} }
status_t status_t
UnixFifo::_Write(Request& request, net_buffer* buffer, bigtime_t timeout, UnixFifo::_Write(UnixRequest& request, bigtime_t timeout)
size_t& bytesWritten)
{ {
if (timeout == 0) if (timeout == 0)
RETURN_ERROR(_WriteNonBlocking(request, buffer, bytesWritten)); RETURN_ERROR(_WriteNonBlocking(request));
// wait for the request to reach the front of the queue // wait for the request to reach the front of the queue
while (fWriters.Head() != &request && !IsWriteShutdown()) { while (fWriters.Head() != &request && !IsWriteShutdown()) {
@@ -571,13 +517,12 @@ UnixFifo::_Write(Request& request, net_buffer* buffer, bigtime_t timeout,
if (IsReadShutdown()) if (IsReadShutdown())
return EPIPE; return EPIPE;
if (request.size == 0) if (request.TotalSize() == 0)
return B_OK; return 0;
status_t error = B_OK; status_t error = B_OK;
size_t bytesLeft = buffer->size;
while (error == B_OK && bytesLeft > 0) { while (error == B_OK && request.BytesRemaining() > 0) {
// wait for any space to become available // wait for any space to become available
while (error == B_OK && fBuffer.Writable() == 0 && !IsWriteShutdown() while (error == B_OK && fBuffer.Writable() == 0 && !IsWriteShutdown()
&& !IsReadShutdown()) { && !IsReadShutdown()) {
@@ -599,14 +544,11 @@ UnixFifo::_Write(Request& request, net_buffer* buffer, bigtime_t timeout,
return EPIPE; return EPIPE;
// write as much as we can // write as much as we can
size_t toWrite = min_c(fBuffer.Writable(), bytesLeft); error = fBuffer.Write(request);
error = fBuffer.Write(buffer, toWrite);
if (error == B_OK) { if (error == B_OK) {
// TODO: Whenever we've successfully written a part, we should reset the // TODO: Whenever we've successfully written a part, we should reset the
// timeout! // timeout!
bytesWritten += toWrite;
bytesLeft -= toWrite;
} }
} }
@@ -615,35 +557,16 @@ UnixFifo::_Write(Request& request, net_buffer* buffer, bigtime_t timeout,
status_t status_t
UnixFifo::_WriteNonBlocking(Request& request, net_buffer* buffer, UnixFifo::_WriteNonBlocking(UnixRequest& request)
size_t& bytesWritten)
{ {
// We need to be first in queue and space should be available right now, // We need to be first in queue and space should be available right now,
// otherwise we need to fail. // otherwise we need to fail.
if (fWriters.Head() != &request || fBuffer.Writable() == 0) if (fWriters.Head() != &request || fBuffer.Writable() == 0)
RETURN_ERROR(B_WOULD_BLOCK); RETURN_ERROR(B_WOULD_BLOCK);
if (request.size == 0) if (request.TotalSize() == 0)
return B_OK; return 0;
// Write as much as we can. // Write as much as we can.
size_t toWrite = min_c(fBuffer.Writable(), buffer->size); RETURN_ERROR(fBuffer.Write(request));
status_t error;
if (buffer->size <= fBuffer.Writable()) {
// enough space available
error = fBuffer.Write(buffer, toWrite);
if (error == B_OK)
bytesWritten = toWrite;
} else {
// not enough space available -- write what we can, but return
// B_WOULD_BLOCK nevertheless
error = fBuffer.Write(buffer,toWrite);
if (error == B_OK) {
bytesWritten = toWrite;
error = B_WOULD_BLOCK;
}
}
RETURN_ERROR(error);
} }
@@ -18,14 +18,40 @@
#define UNIX_FIFO_SHUTDOWN_READ 1 #define UNIX_FIFO_SHUTDOWN_READ 1
#define UNIX_FIFO_SHUTDOWN_WRITE 2 #define UNIX_FIFO_SHUTDOWN_WRITE 2
#define UNIX_FIFO_SHUTDOWN 1 #define UNIX_FIFO_SHUTDOWN (B_ERRORS_END + 1)
// error code returned by Read()/Write() // error code returned by Read()/Write()
#define UNIX_FIFO_MINIMAL_CAPACITY 1024 #define UNIX_FIFO_MINIMAL_CAPACITY 1024
#define UNIX_FIFO_MAXIMAL_CAPACITY (128 * 1024) #define UNIX_FIFO_MAXIMAL_CAPACITY (128 * 1024)
#define TRACE_BUFFER_QUEUE 0 struct ring_buffer;
class UnixRequest : public DoublyLinkedListLinkImpl<UnixRequest> {
public:
UnixRequest(const iovec* vecs, size_t count,
ancillary_data_container* ancillaryData);
off_t TotalSize() const { return fTotalSize; }
off_t BytesTransferred() const { return fBytesTransferred; }
off_t BytesRemaining() const { return fTotalSize - fBytesTransferred; }
void AddBytesTransferred(size_t size);
bool GetCurrentChunk(void*& data, size_t& size);
ancillary_data_container* AncillaryData() const { return fAncillaryData; }
void SetAncillaryData(ancillary_data_container* data);
void AddAncillaryData(ancillary_data_container* data);
private:
const iovec* fVecs;
size_t fVecCount;
ancillary_data_container* fAncillaryData;
off_t fTotalSize;
off_t fBytesTransferred;
size_t fVecIndex;
size_t fVecOffset;
};
class UnixBufferQueue { class UnixBufferQueue {
@@ -33,38 +59,31 @@ public:
UnixBufferQueue(size_t capacity); UnixBufferQueue(size_t capacity);
~UnixBufferQueue(); ~UnixBufferQueue();
size_t Readable() const { return fSize; } status_t Init();
size_t Writable() const
{ return fCapacity >= fSize ? fCapacity - fSize : 0; }
status_t Read(size_t size, net_buffer** _buffer); size_t Readable() const;
status_t Write(net_buffer* buffer, size_t maxSize); size_t Writable() const;
size_t Capacity() const { return fCapacity; } status_t Read(UnixRequest& request);
void SetCapacity(size_t capacity); status_t Write(UnixRequest& request);
#if TRACE_BUFFER_QUEUE size_t Capacity() const { return fCapacity; }
void ParanoiaReadCheck(net_buffer* buffer); status_t SetCapacity(size_t capacity);
void PostReadWrite();
#endif
private: private:
typedef DoublyLinkedList<net_buffer, DoublyLinkedListCLink<net_buffer> > struct AncillaryDataEntry : DoublyLinkedListLinkImpl<AncillaryDataEntry> {
BufferList; ancillary_data_container* data;
size_t offset;
};
BufferList fBuffers; typedef DoublyLinkedList<AncillaryDataEntry> AncillaryDataList;
size_t fSize;
size_t fCapacity; ring_buffer* fBuffer;
#if TRACE_BUFFER_QUEUE size_t fCapacity;
off_t fWritten; AncillaryDataList fAncillaryData;
off_t fRead;
uint8* fParanoiaCheckBuffer;
uint8* fParanoiaCheckBuffer2;
#endif
}; };
class UnixFifo : public Referenceable { class UnixFifo : public Referenceable {
public: public:
UnixFifo(size_t capacity); UnixFifo(size_t capacity);
@@ -94,42 +113,31 @@ public:
return (fShutdown & UNIX_FIFO_SHUTDOWN_WRITE); return (fShutdown & UNIX_FIFO_SHUTDOWN_WRITE);
} }
status_t Read(size_t numBytes, bigtime_t timeout, net_buffer** _buffer); ssize_t Read(const iovec* vecs, size_t vecCount,
status_t Write(net_buffer* buffer, bigtime_t timeout); ancillary_data_container** _ancillaryData, bigtime_t timeout);
ssize_t Write(const iovec* vecs, size_t vecCount,
ancillary_data_container* ancillaryData, bigtime_t timeout);
size_t Readable() const; size_t Readable() const;
size_t Writable() const; size_t Writable() const;
void SetBufferCapacity(size_t capacity); status_t SetBufferCapacity(size_t capacity);
private: private:
struct Request : DoublyLinkedListLinkImpl<Request> { typedef DoublyLinkedList<UnixRequest> RequestList;
Request(size_t size)
:
size(size)
{
}
size_t size;
};
typedef DoublyLinkedList<Request> RequestList;
private: private:
status_t _Read(Request& request, size_t numBytes, bigtime_t timeout, status_t _Read(UnixRequest& request, bigtime_t timeout);
net_buffer** _buffer); status_t _Write(UnixRequest& request, bigtime_t timeout);
status_t _Write(Request& request, net_buffer* buffer, bigtime_t timeout, status_t _WriteNonBlocking(UnixRequest& request);
size_t& bytesWritten);
status_t _WriteNonBlocking(Request& request, net_buffer* buffer,
size_t& bytesWritten);
private: private:
mutex fLock; mutex fLock;
UnixBufferQueue fBuffer; UnixBufferQueue fBuffer;
RequestList fReaders; RequestList fReaders;
RequestList fWriters; RequestList fWriters;
size_t fReadRequested; off_t fReadRequested;
size_t fWriteRequested; off_t fWriteRequested;
ConditionVariable fReadCondition; ConditionVariable fReadCondition;
ConditionVariable fWriteCondition; ConditionVariable fWriteCondition;
uint32 fShutdown; uint32 fShutdown;
@@ -148,7 +148,9 @@ unix_setsockopt(net_protocol *protocol, int level, int option,
if (length != sizeof(int)) if (length != sizeof(int))
return B_BAD_VALUE; return B_BAD_VALUE;
endpoint->SetReceiveBufferSize(*(int*)_value); status_t error = endpoint->SetReceiveBufferSize(*(int*)_value);
if (error != B_OK)
return error;
} else if (option == SO_SNDBUF) { } else if (option == SO_SNDBUF) {
// We don't have a receive buffer, so silently ignore this one. // We don't have a receive buffer, so silently ignore this one.
} }
@@ -198,7 +200,7 @@ unix_send_routed_data(net_protocol *_protocol, struct net_route *route,
status_t status_t
unix_send_data(net_protocol *_protocol, net_buffer *buffer) unix_send_data(net_protocol *_protocol, net_buffer *buffer)
{ {
return ((UnixEndpoint*)_protocol)->Send(buffer); return B_ERROR;
} }
@@ -213,7 +215,7 @@ status_t
unix_read_data(net_protocol *_protocol, size_t numBytes, uint32 flags, unix_read_data(net_protocol *_protocol, size_t numBytes, uint32 flags,
net_buffer **_buffer) net_buffer **_buffer)
{ {
return ((UnixEndpoint*)_protocol)->Receive(numBytes, flags, _buffer); return B_ERROR;
} }
@@ -380,6 +382,25 @@ unix_process_ancillary_data(net_protocol *self,
} }
ssize_t
unix_send_data_no_buffer(net_protocol *_protocol, const iovec *vecs,
size_t vecCount, ancillary_data_container *ancillaryData,
const struct sockaddr *address, socklen_t addressLength)
{
return ((UnixEndpoint*)_protocol)->Send(vecs, vecCount, ancillaryData);
}
ssize_t
unix_read_data_no_buffer(net_protocol *_protocol, const iovec *vecs,
size_t vecCount, ancillary_data_container **_ancillaryData,
struct sockaddr *_address, socklen_t *_addressLength)
{
return ((UnixEndpoint*)_protocol)->Receive(vecs, vecCount, _ancillaryData,
_address, _addressLength);
}
// #pragma mark - // #pragma mark -
@@ -469,7 +490,9 @@ net_protocol_module_info gUnixModule = {
unix_error, unix_error,
unix_error_reply, unix_error_reply,
unix_add_ancillary_data, unix_add_ancillary_data,
unix_process_ancillary_data unix_process_ancillary_data,
unix_send_data_no_buffer,
unix_read_data_no_buffer
}; };
module_dependency module_dependencies[] = { module_dependency module_dependencies[] = {