32
32
33
33
#include < curl/curl.h>
34
34
35
+ #include < algorithm>
35
36
#include < charconv>
37
+ #include < iostream>
36
38
#include < filesystem>
37
39
#include < map>
38
40
#include < memory>
41
43
#include < stdlib.h>
42
44
#include < string>
43
45
#include < string_view>
46
+ #include < thread>
44
47
#include < vector>
45
48
46
49
using namespace XrdHTTPServer ;
@@ -49,6 +52,10 @@ S3FileSystem *g_s3_oss = nullptr;
49
52
50
53
XrdVERSIONINFO (XrdOssGetFileSystem, S3);
51
54
55
+ std::vector<std::pair<std::weak_ptr<std::mutex>, std::weak_ptr<AmazonS3SendMultipartPart>>> S3File::m_pending_ops;
56
+ std::mutex S3File::m_pending_lk;
57
+ std::once_flag S3File::m_monitor_launch;
58
+
52
59
S3File::S3File (XrdSysError &log, S3FileSystem *oss)
53
60
: m_log(log), m_oss(oss), content_length(0 ), last_modified(0 ),
54
61
partNumber(1 ) {}
@@ -61,6 +68,9 @@ int S3File::Open(const char *path, int Oflag, mode_t Mode, XrdOucEnv &env) {
61
68
if (Oflag & O_APPEND) {
62
69
m_log.Log (LogMask::Info, " Open" , " File opened for append:" , path);
63
70
}
71
+ if (Oflag & (O_RDWR | O_WRONLY)) {
72
+ m_write_lk.reset (new std::mutex);
73
+ }
64
74
65
75
char *asize_char;
66
76
if ((asize_char = env.Get (" oss.asize" ))) {
@@ -206,16 +216,28 @@ int S3File::Fstat(struct stat *buff) {
206
216
}
207
217
208
218
ssize_t S3File::Write (const void *buffer, off_t offset, size_t size) {
219
+ auto write_mutex = m_write_lk;
220
+ if (!write_mutex) {
221
+ return -EBADF;
222
+ }
223
+ std::lock_guard lk (*write_mutex);
224
+
209
225
if (offset != m_write_offset) {
210
226
m_log.Emsg (
211
227
" Write" ,
212
228
" Out-of-order write detected; S3 requires writes to be in order" );
229
+ m_write_offset = -1 ;
230
+ return -EIO;
231
+ }
232
+ if (m_write_offset == -1 ) {
233
+ // Previous I/O error has occurred. File is in bad state, immediately fail.
213
234
return -EIO;
214
235
}
215
236
if (uploadId == " " ) {
216
237
AmazonS3CreateMultipartUpload startUpload (m_ai, m_object, m_log);
217
238
if (!startUpload.SendRequest ()) {
218
239
m_log.Emsg (" Write" , " S3 multipart request failed" );
240
+ m_write_offset = -1 ;
219
241
return -ENOENT;
220
242
}
221
243
std::string errMsg;
@@ -240,6 +262,10 @@ ssize_t S3File::Write(const void *buffer, off_t offset, size_t size) {
240
262
}
241
263
242
264
m_write_op.reset (new AmazonS3SendMultipartPart (m_ai, m_object, m_log));
265
+ {
266
+ std::lock_guard lk (m_pending_lk);
267
+ m_pending_ops.emplace_back (m_write_lk, m_write_op);
268
+ }
243
269
244
270
// Calculate the size of the current chunk, if it's known.
245
271
m_part_size = m_s3_part_size;
@@ -271,8 +297,15 @@ ssize_t S3File::ContinueSendPart(const void *buffer, size_t size) {
271
297
if (!m_write_op->SendRequest (
272
298
std::string_view (static_cast <const char *>(buffer), write_size),
273
299
std::to_string (partNumber), uploadId, m_object_size, is_final)) {
300
+ m_write_offset = -1 ;
301
+ if (m_write_op->getErrorCode () == " E_TIMEOUT" ) {
302
+ m_log.Emsg (" Write" , " Timeout when uploading to S3" );
303
+ m_write_op.reset ();
304
+ return -ETIMEDOUT;
305
+ }
274
306
m_log.Emsg (" Write" , " Upload to S3 failed: " ,
275
307
m_write_op->getErrorMessage ().c_str ());
308
+ m_write_op.reset ();
276
309
return -EIO;
277
310
}
278
311
if (is_final) {
@@ -283,13 +316,17 @@ ssize_t S3File::ContinueSendPart(const void *buffer, size_t size) {
283
316
if (startPos == std::string::npos) {
284
317
m_log.Emsg (" Write" , " Result from S3 does not include ETag:" ,
285
318
resultString.c_str ());
319
+ m_write_op.reset ();
320
+ m_write_offset = -1 ;
286
321
return -EIO;
287
322
}
288
323
std::size_t endPos = resultString.find (" \" " , startPos + 7 );
289
324
if (startPos == std::string::npos) {
290
325
m_log.Emsg (" Write" ,
291
326
" Result from S3 does not include ETag end-character:" ,
292
327
resultString.c_str ());
328
+ m_write_op.reset ();
329
+ m_write_offset = -1 ;
293
330
return -EIO;
294
331
}
295
332
eTags.push_back (
@@ -301,6 +338,58 @@ ssize_t S3File::ContinueSendPart(const void *buffer, size_t size) {
301
338
return write_size;
302
339
}
303
340
341
+ void S3File::LaunchMonitorThread () {
342
+ std::call_once (m_monitor_launch, [] {
343
+ std::thread t (S3File::CleanupTransfers);
344
+ t.detach ();
345
+ });
346
+ }
347
+
348
+ void S3File::CleanupTransfers () {
349
+ while (true ) {
350
+ std::this_thread::sleep_for (HTTPRequest::GetStallTimeout ()/3 );
351
+ try {
352
+ CleanupTransfersOnce ();
353
+ } catch (std::exception &exc) {
354
+ std::cerr << " Warning: caught unexpected exception when trying to clean transfers: " << exc.what () << std::endl;
355
+ }
356
+ }
357
+ }
358
+
359
+ void S3File::CleanupTransfersOnce () {
360
+ // Make a list of live transfers; erase any dead ones still on the list.
361
+ std::vector<std::pair<std::shared_ptr<std::mutex>, std::shared_ptr<AmazonS3SendMultipartPart>>> existing_ops;
362
+ {
363
+ std::lock_guard lk (m_pending_lk);
364
+ existing_ops.reserve (m_pending_ops.size ());
365
+ m_pending_ops.erase (std::remove_if (m_pending_ops.begin (), m_pending_ops.end (),
366
+ [&](const auto &op) -> bool {
367
+ auto op_lk = op.first .lock ();
368
+ if (!op_lk) {
369
+ // In this case, the S3File is no longer open for write. No need to potentially
370
+ // clean up the transfer.
371
+ return true ;
372
+ }
373
+ auto op_part = op.second .lock ();
374
+ if (!op_part) {
375
+ // In this case, the S3File object is still open for writes but the upload has completed.
376
+ // Remove from the list.
377
+ return true ;
378
+ }
379
+ // The S3File is open and upload is in-progress; we'll tick the transfer.
380
+ existing_ops.emplace_back (op_lk, op_part);
381
+ return false ;
382
+ }
383
+ ), m_pending_ops.end ());
384
+ }
385
+ // For each live transfer, call `Tick` to advance the clock and possibly time things out.
386
+ auto now = std::chrono::steady_clock::now ();
387
+ for (auto &info : existing_ops) {
388
+ std::lock_guard lk (*info.first );
389
+ info.second ->Tick (now);
390
+ }
391
+ }
392
+
304
393
int S3File::Close (long long *retsz) {
305
394
// If we opened the object in create mode but did not actually write
306
395
// anything, make a quick zero-length file.
@@ -315,6 +404,7 @@ int S3File::Close(long long *retsz) {
315
404
}
316
405
}
317
406
if (m_write_op) {
407
+ std::lock_guard lk (*m_write_lk);
318
408
m_part_size = m_part_written;
319
409
auto written = ContinueSendPart (nullptr , 0 );
320
410
if (written < 0 ) {
@@ -375,6 +465,7 @@ XrdOss *XrdOssGetStorageSystem2(XrdOss *native_oss, XrdSysLogger *Logger,
375
465
376
466
envP->Export (" XRDXROOTD_NOPOSC" , " 1" );
377
467
468
+ S3File::LaunchMonitorThread ();
378
469
try {
379
470
AmazonRequest::Init (*log);
380
471
g_s3_oss = new S3FileSystem (Logger, config_fn, envP);
0 commit comments