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