lab.nexedi.com/kirr/go123@v0.0.0-20240207185015-8299741fa871/xio/xio.go (about) 1 // Copyright (C) 2017-2019 Nexedi SA and Contributors. 2 // Kirill Smelkov <kirr@nexedi.com> 3 // 4 // This program is free software: you can Use, Study, Modify and Redistribute 5 // it under the terms of the GNU General Public License version 3, or (at your 6 // option) any later version, as published by the Free Software Foundation. 7 // 8 // You can also Link and Combine this program with other software covered by 9 // the terms of any of the Free Software licenses or any of the Open Source 10 // Initiative approved licenses and Convey the resulting work. Corresponding 11 // source of such a combination shall include the source code for all other 12 // software used. 13 // 14 // This program is distributed WITHOUT ANY WARRANTY; without even the implied 15 // warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 16 // 17 // See COPYING file for full licensing terms. 18 // See https://www.nexedi.com/licensing for rationale and options. 19 20 // Package xio provides addons to standard package io. 21 // 22 // - Reader, Writer, ReadWriter, etc are io analogs that add support for contexts. 23 // - BindCtx*(X, ctx) converts xio.X into io.X that implicitly passes ctx 24 // to xio.X and can be used in legacy code. 25 // - WithCtx*(X) converts io.X back into xio.X that accepts context. 26 // It is the opposite operation for BindCtx, but for arbitrary io.X 27 // returned xio.X handles context only on best-effort basis. In 28 // particular IO cancellation is not reliably handled for os.File . 29 // - Pipe amends io.Pipe and creates synchronous in-memory pipe that 30 // supports IO cancellation. 31 // 32 // Miscellaneous utilities: 33 // 34 // - CountReader provides InputOffset for a Reader. 35 package xio 36 37 import ( 38 "context" 39 "io" 40 ) 41 42 // Reader is like io.Reader but additionally takes context for Read. 43 type Reader interface { 44 Read(ctx context.Context, dst []byte) (n int, err error) 45 } 46 47 // Writer is like io.Writer but additionally takes context for Write. 48 type Writer interface { 49 Write(ctx context.Context, src []byte) (n int, err error) 50 } 51 52 // ReadWriter combines Reader and Writer. 53 type ReadWriter interface { 54 Reader 55 Writer 56 } 57 58 // ReadCloser combines Reader and io.Closer. 59 type ReadCloser interface { 60 Reader 61 io.Closer 62 } 63 64 // WriteCloser combines Writer and io.Closer. 65 type WriteCloser interface { 66 Writer 67 io.Closer 68 } 69 70 // ReadWriteCloser combines Reader, Writer and io.Closer. 71 type ReadWriteCloser interface { 72 Reader 73 Writer 74 io.Closer 75 } 76 77 78 // BindCtx*(xio.X, ctx) -> io.X 79 // 80 // XXX better just BindCtx(x T, ctx) -> T with all x IO methods without ctx, 81 // but that needs either generics, or support from reflect to preserve optional 82 // methods: https://github.com/golang/go/issues/16522. 83 84 85 // BindCtxR binds Reader r and ctx into io.Reader which passes ctx to r on every Read. 86 func BindCtxR(r Reader, ctx context.Context) io.Reader { 87 // BindCtx(WithCtx(X), BG) = X 88 if ctx.Done() == nil { 89 switch s := r.(type) { 90 case *stubCtxR: return s.r 91 case *stubCtxRW: return s.rw 92 case *stubCtxRC: return s.r 93 case *stubCtxRWC: return s.rw 94 } 95 } 96 97 return &bindCtxR{r, ctx} 98 } 99 type bindCtxR struct {r Reader; ctx context.Context} 100 func (b *bindCtxR) Read(dst []byte) (int, error) { return b.r.Read(b.ctx, dst) } 101 102 // BindCtxW binds Writer w and ctx into io.Writer which passes ctx to w on every Write. 103 func BindCtxW(w Writer, ctx context.Context) io.Writer { 104 if ctx.Done() == nil { 105 switch s := w.(type) { 106 case *stubCtxW: return s.w 107 case *stubCtxRW: return s.rw 108 case *stubCtxWC: return s.w 109 case *stubCtxRWC: return s.rw 110 } 111 } 112 return &bindCtxW{w, ctx} 113 } 114 type bindCtxW struct {w Writer; ctx context.Context} 115 func (b *bindCtxW) Write(src []byte) (int, error) { return b.w.Write(b.ctx, src) } 116 117 // BindCtxRW binds ReadWriter rw and ctx into io.ReadWriter which passes ctx to 118 // rw on every Read and Write. 119 func BindCtxRW(rw ReadWriter, ctx context.Context) io.ReadWriter { 120 if ctx.Done() == nil { 121 switch s := rw.(type) { 122 case *stubCtxRW: return s.rw 123 case *stubCtxRWC: return s.rw 124 } 125 } 126 return &bindCtxRW{rw, ctx} 127 } 128 type bindCtxRW struct {rw ReadWriter; ctx context.Context} 129 func (b *bindCtxRW) Read (dst []byte) (int, error) { return b.rw.Read (b.ctx, dst) } 130 func (b *bindCtxRW) Write(src []byte) (int, error) { return b.rw.Write(b.ctx, src) } 131 132 // BindCtxRC binds ReadCloser r and ctx into io.ReadCloser which passes ctx to r on every Read. 133 func BindCtxRC(r ReadCloser, ctx context.Context) io.ReadCloser { 134 if ctx.Done() == nil { 135 switch s := r.(type) { 136 case *stubCtxRC: return s.r 137 case *stubCtxRWC: return s.rw 138 } 139 } 140 return &bindCtxRC{r, ctx} 141 } 142 type bindCtxRC struct {r ReadCloser; ctx context.Context} 143 func (b *bindCtxRC) Read(dst []byte) (int, error) { return b.r.Read(b.ctx, dst) } 144 func (b *bindCtxRC) Close() error { return b.r.Close() } 145 146 // BindCtxWC binds WriteCloser w and ctx into io.WriteCloser which passes ctx to w on every Write. 147 func BindCtxWC(w WriteCloser, ctx context.Context) io.WriteCloser { 148 if ctx.Done() == nil { 149 switch s := w.(type) { 150 case *stubCtxWC: return s.w 151 case *stubCtxRWC: return s.rw 152 } 153 } 154 return &bindCtxWC{w, ctx} 155 } 156 type bindCtxWC struct {w WriteCloser; ctx context.Context} 157 func (b *bindCtxWC) Write(src []byte) (int, error) { return b.w.Write(b.ctx, src) } 158 func (b *bindCtxWC) Close() error { return b.w.Close() } 159 160 // BindCtxRWC binds ReadWriteCloser rw and ctx into io.ReadWriteCloser 161 // which passes ctx to rw on every Read and Write. 162 func BindCtxRWC(rw ReadWriteCloser, ctx context.Context) io.ReadWriteCloser { 163 if ctx.Done() == nil { 164 switch s := rw.(type) { 165 case *stubCtxRWC: return s.rw 166 } 167 } 168 return &bindCtxRWC{rw, ctx} 169 } 170 type bindCtxRWC struct {rw ReadWriteCloser; ctx context.Context} 171 func (b *bindCtxRWC) Read(dst []byte) (int, error) { return b.rw.Read(b.ctx, dst) } 172 func (b *bindCtxRWC) Write(src []byte) (int, error) { return b.rw.Write(b.ctx, src) } 173 func (b *bindCtxRWC) Close() error { return b.rw.Close() } 174 175 176 // WithCtx*(io.X) -> xio.X that handles ctx on best-effort basis. 177 // 178 // FIXME for arbitrary io.X for now ctx is completely ignored. 179 // TODO add support for cancellation if io.X provides working .Set{Read/Write}Deadline: 180 // https://medium.com/@zombiezen/canceling-i-o-in-go-capn-proto-5ae8c09c5b29 181 // https://github.com/golang/go/issues/20280 182 183 // WithCtxR converts io.Reader r into Reader that accepts ctx. 184 // 185 // It returns original IO object if r was created via BindCtx*, but in general 186 // returned Reader will handle context only on best-effort basis. 187 func WithCtxR(r io.Reader) Reader { 188 // WithCtx(BindCtx(X)) = X 189 switch b := r.(type) { 190 case *bindCtxR: return b.r 191 case *bindCtxRW: return b.rw 192 case *bindCtxRC: return b.r 193 case *bindCtxRWC: return b.rw 194 } 195 196 return &stubCtxR{r} 197 } 198 type stubCtxR struct {r io.Reader} 199 func (s *stubCtxR) Read(ctx context.Context, dst []byte) (int, error) { return s.r.Read(dst) } 200 201 // WithCtxW converts io.Writer w into Writer that accepts ctx. 202 // 203 // It returns original IO object if w was created via BindCtx*, but in general 204 // returned Writer will handle context only on best-effort basis. 205 func WithCtxW(w io.Writer) Writer { 206 switch b := w.(type) { 207 case *bindCtxW: return b.w 208 case *bindCtxRW: return b.rw 209 case *bindCtxWC: return b.w 210 case *bindCtxRWC: return b.rw 211 } 212 return &stubCtxW{w} 213 } 214 type stubCtxW struct {w io.Writer} 215 func (s *stubCtxW) Write(ctx context.Context, src []byte) (int, error) { return s.w.Write(src) } 216 217 // WithCtxRW converts io.ReadWriter rw into ReadWriter that accepts ctx. 218 // 219 // It returns original IO object if rw was created via BindCtx*, but in general 220 // returned ReadWriter will handle context only on best-effort basis. 221 func WithCtxRW(rw io.ReadWriter) ReadWriter { 222 switch b := rw.(type) { 223 case *bindCtxRW: return b.rw 224 case *bindCtxRWC: return b.rw 225 } 226 return &stubCtxRW{rw} 227 } 228 type stubCtxRW struct {rw io.ReadWriter} 229 func (s *stubCtxRW) Read (ctx context.Context, dst []byte) (int, error) { return s.rw.Read (dst) } 230 func (s *stubCtxRW) Write(ctx context.Context, src []byte) (int, error) { return s.rw.Write(src) } 231 232 // WithCtxRC converts io.ReadCloser r into ReadCloser that accepts ctx. 233 // 234 // It returns original IO object if r was created via BindCtx*, but in general 235 // returned ReadCloser will handle context only on best-effort basis. 236 func WithCtxRC(r io.ReadCloser) ReadCloser { 237 switch b := r.(type) { 238 case *bindCtxRC: return b.r 239 case *bindCtxRWC: return b.rw 240 } 241 return &stubCtxRC{r} 242 } 243 type stubCtxRC struct {r io.ReadCloser} 244 func (s *stubCtxRC) Read (ctx context.Context, dst []byte) (int, error) { return s.r.Read(dst) } 245 func (s *stubCtxRC) Close() error { return s.r.Close() } 246 247 // WithCtxWC converts io.WriteCloser w into WriteCloser that accepts ctx. 248 // 249 // It returns original IO object if w was created via BindCtx*, but in general 250 // returned WriteCloser will handle context only on best-effort basis. 251 func WithCtxWC(w io.WriteCloser) WriteCloser { 252 switch b := w.(type) { 253 case *bindCtxWC: return b.w 254 case *bindCtxRWC: return b.rw 255 } 256 return &stubCtxWC{w} 257 } 258 type stubCtxWC struct {w io.WriteCloser} 259 func (s *stubCtxWC) Write(ctx context.Context, src []byte) (int, error) { return s.w.Write(src) } 260 func (s *stubCtxWC) Close() error { return s.w.Close() } 261 262 // WithCtxRWC converts io.ReadWriteCloser rw into ReadWriteCloser that accepts ctx. 263 // 264 // It returns original IO object if rw was created via BindCtx*, but in general 265 // returned ReadWriteCloser will handle context only on best-effort basis. 266 func WithCtxRWC(rw io.ReadWriteCloser) ReadWriteCloser { 267 switch b := rw.(type) { 268 case *bindCtxRWC: return b.rw 269 } 270 return &stubCtxRWC{rw} 271 } 272 type stubCtxRWC struct {rw io.ReadWriteCloser} 273 func (s *stubCtxRWC) Read (ctx context.Context, dst []byte) (int, error){ return s.rw.Read (dst) } 274 func (s *stubCtxRWC) Write(ctx context.Context, src []byte) (int, error){ return s.rw.Write(src) } 275 func (s *stubCtxRWC) Close() error { return s.rw.Close() } 276 277 278 // ---------------------------------------- 279 280 281 // CountedReader is a Reader that count total bytes read. 282 type CountedReader struct { 283 r Reader 284 nread int64 285 } 286 287 func (cr *CountedReader) Read(ctx context.Context, p []byte) (int, error) { 288 n, err := cr.r.Read(ctx, p) 289 cr.nread += int64(n) 290 return n, err 291 } 292 293 // InputOffset returns the number of bytes read. 294 func (cr *CountedReader) InputOffset() int64 { 295 return cr.nread 296 } 297 298 // CountReader wraps r with CountedReader. 299 func CountReader(r Reader) *CountedReader { 300 return &CountedReader{r, 0} 301 }