From 422b8472fec9443250895a281b6b0a20190daa22 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Wed, 6 Nov 2013 20:36:37 +0100
Subject: [PATCH] event/FullyBufferedSocket: try to write without extra
 roundtrip

Postpone the write using IdleMonitor instead of scheduling a write
event.  This reduces the number of system calls, because we don't need
to register and unregister the write event in epoll.
---
 src/event/FullyBufferedSocket.cxx | 38 +++++++++++++++++++------------
 src/event/FullyBufferedSocket.hxx | 12 +++++++---
 2 files changed, 33 insertions(+), 17 deletions(-)

diff --git a/src/event/FullyBufferedSocket.cxx b/src/event/FullyBufferedSocket.cxx
index 6a0df183d..3b8cf3765 100644
--- a/src/event/FullyBufferedSocket.cxx
+++ b/src/event/FullyBufferedSocket.cxx
@@ -42,7 +42,8 @@ FullyBufferedSocket::DirectWrite(const void *data, size_t length)
 		if (IsSocketErrorAgain(code))
 			return 0;
 
-		Cancel();
+		IdleMonitor::Cancel();
+		BufferedSocket::Cancel();
 
 		if (IsSocketErrorClosed(code))
 			OnSocketClosed();
@@ -61,6 +62,7 @@ FullyBufferedSocket::Flush()
 	size_t length;
 	const void *data = output.Read(&length);
 	if (data == nullptr) {
+		IdleMonitor::Cancel();
 		CancelWrite();
 		return true;
 	}
@@ -71,8 +73,10 @@ FullyBufferedSocket::Flush()
 
 	output.Consume(nbytes);
 
-	if (output.IsEmpty())
+	if (output.IsEmpty()) {
+		IdleMonitor::Cancel();
 		CancelWrite();
+	}
 
 	return true;
 }
@@ -82,6 +86,9 @@ FullyBufferedSocket::Write(const void *data, size_t length)
 {
 	assert(IsDefined());
 
+	if (length == 0)
+		return true;
+
 #if 0
 	/* TODO: disabled because this would add overhead on some callers (the ones that often), but it may be useful */
 
@@ -98,6 +105,8 @@ FullyBufferedSocket::Write(const void *data, size_t length)
 	}
 #endif
 
+	const bool was_empty = output.IsEmpty();
+
 	if (!output.Append(data, length)) {
 		// TODO
 		static constexpr Domain buffered_socket_domain("buffered_socket");
@@ -107,30 +116,31 @@ FullyBufferedSocket::Write(const void *data, size_t length)
 		return false;
 	}
 
-	ScheduleWrite();
+	if (was_empty)
+		IdleMonitor::Schedule();
 	return true;
 }
 
 bool
 FullyBufferedSocket::OnSocketReady(unsigned flags)
 {
-	const bool was_empty = output.IsEmpty();
-	if (!BufferedSocket::OnSocketReady(flags))
-		return false;
-
-	if (was_empty && !output.IsEmpty())
-		/* just in case the OnSocketInput() method has added
-		   data to the output buffer: try to send it now
-		   instead of waiting for the next event loop
-		   iteration */
-		flags |= WRITE;
-
 	if (flags & WRITE) {
 		assert(!output.IsEmpty());
+		assert(!IdleMonitor::IsActive());
 
 		if (!Flush())
 			return false;
 	}
 
+	if (!BufferedSocket::OnSocketReady(flags))
+		return false;
+
 	return true;
 }
+
+void
+FullyBufferedSocket::OnIdle()
+{
+	if (Flush() && !output.IsEmpty())
+		ScheduleWrite();
+}
diff --git a/src/event/FullyBufferedSocket.hxx b/src/event/FullyBufferedSocket.hxx
index 90638e60b..c50bb5f61 100644
--- a/src/event/FullyBufferedSocket.hxx
+++ b/src/event/FullyBufferedSocket.hxx
@@ -22,24 +22,29 @@
 
 #include "check.h"
 #include "BufferedSocket.hxx"
+#include "IdleMonitor.hxx"
 #include "util/PeakBuffer.hxx"
 #include "Compiler.h"
 
 /**
  * A #BufferedSocket specialization that adds an output buffer.
  */
-class FullyBufferedSocket : protected BufferedSocket {
+class FullyBufferedSocket : protected BufferedSocket, private IdleMonitor {
 	PeakBuffer output;
 
 public:
 	FullyBufferedSocket(int _fd, EventLoop &_loop,
 			    size_t normal_size, size_t peak_size=0)
-		:BufferedSocket(_fd, _loop),
+		:BufferedSocket(_fd, _loop), IdleMonitor(_loop),
 		 output(normal_size, peak_size) {
 	}
 
 	using BufferedSocket::IsDefined;
-	using BufferedSocket::Close;
+
+	void Close() {
+		IdleMonitor::Cancel();
+		BufferedSocket::Close();
+	}
 
 private:
 	ssize_t DirectWrite(const void *data, size_t length);
@@ -58,6 +63,7 @@ protected:
 	bool Write(const void *data, size_t length);
 
 	virtual bool OnSocketReady(unsigned flags) override;
+	virtual void OnIdle() override;
 };
 
 #endif