writer.go 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. /*
  2. Copyright 2023 Docker Compose CLI authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package sync
  14. import (
  15. "errors"
  16. "io"
  17. )
  18. // lossyMultiWriter attempts to tee all writes to the provided io.PipeWriter
  19. // instances.
  20. //
  21. // If a writer fails during a Write call, the write-side of the pipe is then
  22. // closed with the error and no subsequent attempts are made to write to the
  23. // pipe.
  24. //
  25. // If all writers fail during a write, an error is returned.
  26. //
  27. // On Close, any remaining writers are closed.
  28. type lossyMultiWriter struct {
  29. writers []*io.PipeWriter
  30. }
  31. // newLossyMultiWriter creates a new writer that *attempts* to tee all data written to it to the provided io.PipeWriter
  32. // instances. Rather than failing a write operation if any writer fails, writes only fail if there are no more valid
  33. // writers. Otherwise, errors for specific writers are propagated via CloseWithError.
  34. func newLossyMultiWriter(writers ...*io.PipeWriter) *lossyMultiWriter {
  35. // reverse the writers because during the write we iterate
  36. // backwards, so this way we'll end up writing in the same
  37. // order as the writers were passed to us
  38. writers = append([]*io.PipeWriter(nil), writers...)
  39. for i, j := 0, len(writers)-1; i < j; i, j = i+1, j-1 {
  40. writers[i], writers[j] = writers[j], writers[i]
  41. }
  42. return &lossyMultiWriter{
  43. writers: writers,
  44. }
  45. }
  46. // Write writes to each writer that is still active (i.e. has not failed/encountered an error on write).
  47. //
  48. // If a writer encounters an error during the write, the write side of the pipe is closed with the error
  49. // and no subsequent attempts will be made to write to that writer.
  50. //
  51. // An error is only returned from this function if ALL writers have failed.
  52. func (l *lossyMultiWriter) Write(p []byte) (int, error) {
  53. // NOTE: this function iterates backwards so that it can
  54. // safely remove elements during the loop
  55. for i := len(l.writers) - 1; i >= 0; i-- {
  56. written, err := l.writers[i].Write(p)
  57. if err == nil && written != len(p) {
  58. err = io.ErrShortWrite
  59. }
  60. if err != nil {
  61. // pipe writer close cannot fail
  62. _ = l.writers[i].CloseWithError(err)
  63. l.writers = append(l.writers[:i], l.writers[i+1:]...)
  64. }
  65. }
  66. if len(l.writers) == 0 {
  67. return 0, errors.New("no writers remaining")
  68. }
  69. return len(p), nil
  70. }
  71. // Close closes any still open (non-failed) writers.
  72. //
  73. // Failed writers have already been closed with an error.
  74. func (l *lossyMultiWriter) Close() {
  75. for i := range l.writers {
  76. // pipe writer close cannot fail
  77. _ = l.writers[i].Close()
  78. }
  79. }