2020package bolt
2121
2222import (
23+ "context"
2324 "encoding/binary"
25+ rio "github.com/neo4j/neo4j-go-driver/v4/neo4j/internal/racingio"
2426 "github.com/neo4j/neo4j-go-driver/v4/neo4j/log"
25- "io"
2627 "net"
2728 "time"
2829)
2930
3031// dechunkMessage takes a buffer to be reused and returns the reusable buffer
3132// (might have been reallocated to handle growth), the message buffer and
3233// error.
33- // If a non-default connection read timeout configuration hint is passed, the dechunker resets the connection read
34- // deadline as well after successfully reading a chunk (NOOP messages included)
35- func dechunkMessage (conn net.Conn , msgBuf []byte , readTimeout time.Duration ,
36- logger log.Logger , logName , logId string ) ([]byte , []byte , error ) {
34+ // Reads will race against the provided context ctx
35+ // If the server provides the connection read timeout hint readTimeout, a new context will be created from that timeout
36+ // and the user-provided context ctx before every read
37+ func dechunkMessage (
38+ conn net.Conn ,
39+ msgBuf []byte ,
40+ readTimeout time.Duration ,
41+ logger log.Logger ,
42+ logName string ,
43+ logId string ) ([]byte , []byte , error ) {
44+
3745 sizeBuf := []byte {0x00 , 0x00 }
3846 off := 0
3947
48+ reader := rio .NewRacingReader (conn )
49+
4050 for {
41- _ , err := io .ReadFull (conn , sizeBuf )
51+ updatedCtx , cancelFunc := newContext (readTimeout , logger , logName , logId )
52+ _ , err := reader .ReadFull (updatedCtx , sizeBuf )
4253 if err != nil {
4354 return msgBuf , nil , err
4455 }
56+ if cancelFunc != nil { // reading has been completed, time to release the context
57+ cancelFunc ()
58+ }
4559 chunkSize := int (binary .BigEndian .Uint16 (sizeBuf ))
4660 if chunkSize == 0 {
4761 if off > 0 {
4862 return msgBuf , msgBuf [:off ], nil
4963 }
5064 // Got a nop chunk
51- resetConnectionReadDeadline (conn , readTimeout , logger ,
52- logName , logId )
5365 continue
5466 }
5567
@@ -60,20 +72,42 @@ func dechunkMessage(conn net.Conn, msgBuf []byte, readTimeout time.Duration,
6072 msgBuf = newMsgBuf
6173 }
6274 // Read the chunk into buffer
63- _ , err = io .ReadFull (conn , msgBuf [off :(off + chunkSize )])
75+ updatedCtx , cancelFunc = newContext (readTimeout , logger , logName , logId )
76+ _ , err = reader .ReadFull (updatedCtx , msgBuf [off :(off + chunkSize )])
6477 if err != nil {
6578 return msgBuf , nil , err
6679 }
80+ if cancelFunc != nil { // reading has been completed, time to release the context
81+ cancelFunc ()
82+ }
6783 off += chunkSize
68- resetConnectionReadDeadline (conn , readTimeout , logger , logName , logId )
6984 }
7085}
7186
72- func resetConnectionReadDeadline (conn net.Conn , readTimeout time.Duration , logger log.Logger , logName , logId string ) {
73- if readTimeout < 0 {
74- return
87+ // newContext computes a new context and cancel function if a readTimeout is set
88+ func newContext (
89+ readTimeout time.Duration ,
90+ logger log.Logger ,
91+ logName string ,
92+ logId string ) (context.Context , context.CancelFunc ) {
93+
94+ ctx := context .Background ()
95+ if readTimeout >= 0 {
96+ newCtx , cancelFunc := context .WithTimeout (ctx , readTimeout )
97+ logger .Debugf (logName , logId ,
98+ "read timeout of %s applied, chunk read deadline is now: %s" ,
99+ readTimeout .String (),
100+ deadlineOf (newCtx ),
101+ )
102+ return newCtx , cancelFunc
75103 }
76- if err := conn .SetReadDeadline (time .Now ().Add (readTimeout )); err != nil {
77- logger .Error (logName , logId , err )
104+ return ctx , nil
105+ }
106+
107+ func deadlineOf (ctx context.Context ) string {
108+ deadline , hasDeadline := ctx .Deadline ()
109+ if ! hasDeadline {
110+ return "N/A (no deadline set)"
78111 }
112+ return deadline .String ()
79113}
0 commit comments