(dst WriterCloser, src Reader, dir string, status *bidirectionalStreamStatus, log *zerolog.Logger)
| 108 | } |
| 109 | |
| 110 | func unidirectionalStream(dst WriterCloser, src Reader, dir string, status *bidirectionalStreamStatus, log *zerolog.Logger) { |
| 111 | defer func() { |
| 112 | // The bidirectional streaming spawns 2 goroutines to stream each direction. |
| 113 | // If any ends, the callstack returns, meaning the Tunnel request/stream (depending on http2 vs quic) will |
| 114 | // close. In such case, if the other direction did not stop (due to application level stopping, e.g., if a |
| 115 | // server/origin listens forever until closure), it may read/write from the underlying ReadWriter (backed by |
| 116 | // the Edge<->cloudflared transport) in an unexpected state. |
| 117 | // Because of this, we set this recover() logic. |
| 118 | if err := recover(); err != nil { |
| 119 | if status.isAnyDone() { |
| 120 | // We handle such unexpected errors only when we detect that one side of the streaming is done. |
| 121 | log.Debug().Msgf("recovered from panic in stream.Pipe for %s, error %s, %s", dir, err, debug.Stack()) |
| 122 | } else { |
| 123 | // Otherwise, this is unexpected, but we prevent the program from crashing anyway. |
| 124 | log.Warn().Msgf("recovered from panic in stream.Pipe for %s, error %s, %s", dir, err, debug.Stack()) |
| 125 | sentry.CurrentHub().Recover(err) |
| 126 | sentry.Flush(time.Second * 5) |
| 127 | } |
| 128 | } |
| 129 | }() |
| 130 | |
| 131 | defer func() { _ = dst.CloseWrite() }() |
| 132 | |
| 133 | _, err := copyData(dst, src, dir) |
| 134 | if err != nil { |
| 135 | log.Debug().Msgf("%s copy: %v", dir, err) |
| 136 | } |
| 137 | status.markUniStreamDone() |
| 138 | } |
| 139 | |
| 140 | // when set to true, enables logging of content copied to/from origin and tunnel |
| 141 | const debugCopy = false |
no test coverage detected