XRootD
XrdHttpTpcStream.hh
Go to the documentation of this file.
1 
11 
12 #include <memory>
13 #include <vector>
14 #include <string>
15 
16 #include <cstring>
17 
18 struct stat;
19 
20 class XrdSysError;
21 
22 namespace TPC {
23 class Stream {
24 public:
25  Stream(std::unique_ptr<XrdSfsFile> fh, size_t max_blocks, size_t buffer_size, XrdSysError &log)
26  : m_open_for_write(false),
27  m_avail_count(max_blocks),
28  m_fh(std::move(fh)),
29  m_offset(0),
30  m_log(log)
31  {
32  m_buffers.reserve(max_blocks);
33  for (size_t idx=0; idx < max_blocks; idx++) {
34  m_buffers.push_back(std::make_unique<Entry>(buffer_size));
35  }
36  m_open_for_write = true;
37  }
38 
39  ~Stream();
40 
41  int Stat(struct stat *);
42 
43  int Read(off_t offset, char *buffer, size_t size);
44 
45  // Writes a buffer of a given size to an offset.
46  // This will often keep the buffer in memory in to present the underlying
47  // filesystem with a single stream of data (required for HDFS); further,
48  // it will also buffer to align the writes on a 1MB boundary (required
49  // for some RADOS configurations). When force is set to true, it will
50  // skip the buffering and always write (this should only be done at the
51  // end of a stream!).
52  //
53  // Returns the number of bytes written; on error, returns -1 and sets
54  // the error code and error message for the stream
55  ssize_t Write(off_t offset, const char *buffer, size_t size, bool force);
56 
57  // Force the data still held in the re-ordering buffers out to the underlying
58  // file handle, even if it results in unaligned or short writes. Typically
59  // only done while shutting down the transfer.
60  //
61  // The flush is deliberately issued at the current offset of the stream: the
62  // offset a given transfer state stopped at is not necessarily the offset the
63  // stream has been written up to. In the multistream case, all the states
64  // share this stream, and all but the one that happened to serve the last
65  // range end up before it -- flushing at their offset would be rejected as a
66  // write to a prior offset.
67  //
68  // Returns 0 on success; SFS_ERROR on failure.
69  ssize_t Flush() {return Write(m_offset, nullptr, 0, true);}
70 
71  size_t AvailableBuffers() const {return m_avail_count;}
72 
73  void DumpBuffers() const;
74 
75  // Flush and finalize the stream. If all data has been sent to the underlying
76  // file handle, close() will be invoked on the file handle.
77  //
78  // Further write operations on this stream will result in an error.
79  // If any memory buffers remain, an error occurs.
80  //
81  // Returns true on success; false otherwise.
82  bool Finalize();
83 
84  std::string GetErrorMessage() const {return m_error_buf;}
85 
86 private:
87 
88  class Entry {
89  public:
90  Entry(size_t capacity) :
91  m_offset(-1),
92  m_capacity(capacity),
93  m_size(0)
94  {}
95 
96  bool Available() const {return m_offset == -1;}
97 
98  // Writes the contents of this buffer out to the stream, returning the
99  // number of bytes written (0 if the buffer is not eligible for a write
100  // yet) or SFS_ERROR. On success the buffer is emptied and becomes
101  // available again.
102  ssize_t Write(Stream &stream, bool force) {
103  if (Available() || !CanWrite(stream)) {return 0;}
104  // Only full buffer writes are accepted unless the stream forces a flush
105  // (i.e., we are at EOF) because the multistream code uses buffer occupancy
106  // to determine how many streams are currently in-flight. If we do an early
107  // write, then the buffer will be empty and the multistream code may decide
108  // to start another request (which we don't have the capacity to serve!).
109  if (!force && (m_size != m_capacity)) {
110  return 0;
111  }
112  ssize_t retval = stream.WriteImpl(m_offset, &m_buffer[0], m_size);
113  // Currently the only valid negative value is SFS_ERROR (-1); checking for
114  // all negative values to future-proof the code.
115  if ((retval < 0) || (static_cast<size_t>(retval) != m_size)) {
116  return -1;
117  }
118  m_offset = -1;
119  m_size = 0;
120  m_buffer.clear();
121  return retval;
122  }
123 
124  size_t Accept(off_t offset, const char *buf, size_t size) {
125  // Validate acceptance criteria.
126  if ((m_offset != -1) && (offset != m_offset + static_cast<ssize_t>(m_size))) {
127  return 0;
128  }
129  size_t to_accept = m_capacity - m_size;
130  if (to_accept == 0) {return 0;}
131  if (size > to_accept) {
132  size = to_accept;
133  }
134 
135  // Inflate the underlying buffer if needed.
136  ssize_t new_bytes_needed = (m_size + size) - m_buffer.size();
137  if (new_bytes_needed > 0) {
138  m_buffer.resize(m_capacity);
139  }
140 
141  // Finally, do the copy.
142  memcpy(&m_buffer[0] + m_size, buf, size);
143  m_size += size;
144  if (m_offset == -1) {
145  m_offset = offset;
146  }
147  return size;
148  }
149 
150  void ShrinkIfUnused() {
151  if (!Available()) {return;}
152  m_buffer.shrink_to_fit();
153  }
154 
155  off_t GetOffset() const {return m_offset;}
156  size_t GetCapacity() const {return m_capacity;}
157  size_t GetSize() const {return m_size;}
158 
159  private:
160 
161  Entry(const Entry&) = delete;
162 
163  bool CanWrite(Stream &stream) const {
164  return (m_size > 0) && (m_offset == stream.m_offset);
165  }
166 
167  off_t m_offset; // Offset within file that m_buffer[0] represents.
168  size_t m_capacity;
169  size_t m_size; // Number of bytes held in buffer.
170  std::vector<char> m_buffer;
171  };
172 
173  ssize_t WriteImpl(off_t offset, const char *buffer, size_t size);
174 
175  // Copies as much of [buffer, buffer+size) as possible into the buffers that
176  // are already holding data and can be extended contiguously. This is pure
177  // bookkeeping: it never touches the underlying filesystem.
178  //
179  // Returns the number of bytes consumed.
180  size_t AcceptIntoBuffers(off_t offset, const char *buffer, size_t size);
181 
182  // Writes out every buffer that is contiguous with m_offset, repeating until
183  // no further progress is made: flushing one buffer advances m_offset, which
184  // can in turn make another buffer writable. Only completely full buffers
185  // are written unless force is set (see Entry::Write).
186  //
187  // This is the only place where m_avail_count is computed.
188  //
189  // Returns the number of buffers written out, or SFS_ERROR.
190  ssize_t FlushBuffers(bool force);
191 
192  // Returns the first empty buffer, or nullptr if all of them hold data.
193  Entry *FirstAvailableBuffer();
194 
195  bool m_open_for_write;
196  size_t m_avail_count;
197  std::unique_ptr<XrdSfsFile> m_fh;
198  off_t m_offset;
199  std::vector<std::unique_ptr<Entry>> m_buffers;
200  XrdSysError &m_log;
201  std::string m_error_buf;
202 };
203 }
#define stat(a, b)
Definition: XrdPosix.hh:105
int Read(off_t offset, char *buffer, size_t size)
ssize_t Write(off_t offset, const char *buffer, size_t size, bool force)
Stream(std::unique_ptr< XrdSfsFile > fh, size_t max_blocks, size_t buffer_size, XrdSysError &log)
void DumpBuffers() const
std::string GetErrorMessage() const
size_t AvailableBuffers() const
int Stat(struct stat *)
ssize_t Flush()