From b9b48a3e2d1c9dca2e0b2c9668e69dc892a2efee Mon Sep 17 00:00:00 2001
From: Sergey Matveev <stargrave@stargrave.org>
Date: Fri, 21 May 2021 17:36:40 +0300
Subject: [PATCH] UCSPI connection closing

---
 main.go  |  2 +-
 ucspi.go | 35 ++++++++++++++++++++++++++++++++---
 2 files changed, 33 insertions(+), 4 deletions(-)

diff --git a/main.go b/main.go
index a6109ac..8a39053 100644
--- a/main.go
+++ b/main.go
@@ -229,9 +229,9 @@ func main() {
 		ReadHeaderTimeout: 10 * time.Second,
 		MaxHeaderBytes:    10 * (1 << 10),
 	}
-	s.SetKeepAlivesEnabled(false)
 	http.HandleFunc("/upload/", upload)
 	if *doUCSPI {
+		s.SetKeepAlivesEnabled(false)
 		ln := &UCSPI{}
 		s.ConnState = connStater
 		err := s.Serve(ln)
diff --git a/ucspi.go b/ucspi.go
index 74c5b79..105bae0 100644
--- a/ucspi.go
+++ b/ucspi.go
@@ -18,12 +18,15 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 package main
 
 import (
+	"io"
 	"net"
 	"net/http"
 	"os"
 	"time"
 )
 
+var aLongTimeAgo = time.Unix(1, 0)
+
 type UCSPIAddr struct {
 	ip   string
 	port string
@@ -33,9 +36,28 @@ func (addr *UCSPIAddr) Network() string { return "tcp" }
 
 func (addr *UCSPIAddr) String() string { return addr.ip + ":" + addr.port }
 
-type UCSPIConn struct{}
+type UCSPIConn struct {
+	eof chan struct{}
+}
 
-func (conn *UCSPIConn) Read(b []byte) (int, error) { return os.Stdin.Read(b) }
+type ReadResult struct {
+	n   int
+	err error
+}
+
+func (conn *UCSPIConn) Read(b []byte) (int, error) {
+	c := make(chan ReadResult)
+	go func() {
+		n, err := os.Stdin.Read(b)
+		c <- ReadResult{n, err}
+	}()
+	select {
+	case res := <-c:
+		return res.n, res.err
+	case <-conn.eof:
+		return 0, io.EOF
+	}
+}
 
 func (conn *UCSPIConn) Write(b []byte) (int, error) { return os.Stdout.Write(b) }
 
@@ -62,6 +84,12 @@ func (conn *UCSPIConn) SetDeadline(t time.Time) error {
 }
 
 func (conn *UCSPIConn) SetReadDeadline(t time.Time) error {
+	// An ugly hack to forcefully terminate pending read.
+	// net/http calls SetReadDeadline(aLongTimeAgo), but file
+	// descriptors are not capable to exit immediately that way.
+	if t.Equal(aLongTimeAgo) {
+		conn.eof <- struct{}{}
+	}
 	return os.Stdin.SetReadDeadline(t)
 }
 
@@ -82,7 +110,8 @@ func (ucspi *UCSPI) Accept() (net.Conn, error) {
 		return nil, UCSPIAlreadyAccepted{}
 	}
 	ucspi.accepted = true
-	return &UCSPIConn{}, nil
+	conn := UCSPIConn{eof: make(chan struct{}, 1)}
+	return &conn, nil
 }
 
 func (ucspi *UCSPI) Close() error {
-- 
2.51.0