diff options
author | Olivier Goffart <ogoffart@woboq.com> | 2017-12-11 21:25:51 +0300 |
---|---|---|
committer | Olivier Goffart <olivier@woboq.com> | 2017-12-14 13:56:12 +0300 |
commit | 4dc49ff3b01f196dfac0ac928688d90a001b49e8 (patch) | |
tree | 7cf9995f7c23c88e70b1b11dd05e1ddfa54ca6c0 /test | |
parent | 4369853ddb21525a9b83f83ef3d44dd838a17d8d (diff) |
SyncEngine: Recover when the PUT reply (or chunkin's MOVE) is lost
This can happen if the upload of a file is finished, but we just got
disconnected right before recieving the reply containing the etag.
So nothing was save din the DB, and we are not sure if the server
recieved the file properly or not. Further local update of the file
will cause a conflict.
In order to fix this, store the checksum of the uploading file in
the uploadinfo table of the local db (even if there is no chunking
involved). And when we have a conflict, check that it is not because
of this situation by checking the entry in the uploadinfo table.
Issue #5106
Diffstat (limited to 'test')
-rw-r--r-- | test/syncenginetestutils.h | 13 | ||||
-rw-r--r-- | test/testchunkingng.cpp | 70 |
2 files changed, 70 insertions, 13 deletions
diff --git a/test/syncenginetestutils.h b/test/syncenginetestutils.h index 9122040be..f7cf4eb46 100644 --- a/test/syncenginetestutils.h +++ b/test/syncenginetestutils.h @@ -452,7 +452,11 @@ public: emit finished(); } - void abort() override { } + void abort() override + { + setError(OperationCanceledError, "abort"); + emit finished(); + } qint64 readData(char *, qint64) override { return 0; } }; @@ -696,7 +700,12 @@ public: emit finished(); } - void abort() override { } + void abort() override + { + setError(OperationCanceledError, "abort"); + emit finished(); + } + qint64 readData(char *, qint64) override { return 0; } }; diff --git a/test/testchunkingng.cpp b/test/testchunkingng.cpp index a61bdc15d..ce7880537 100644 --- a/test/testchunkingng.cpp +++ b/test/testchunkingng.cpp @@ -161,17 +161,6 @@ private slots: QVERIFY(!moveChecksumHeader.isEmpty()); fakeFolder.remoteModifier().find("A/a0")->checksums = moveChecksumHeader; - // This time it's a real conflict, we have a remote checksum! - connection = connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToPropagate, [&](SyncFileItemVector &items) { - SyncFileItemPtr a0; - for (auto &item : items) { - if (item->_file == "A/a0") - a0 = item; - } - - QVERIFY(a0); - QCOMPARE(a0->_instruction, CSYNC_INSTRUCTION_CONFLICT); - }); QVERIFY(fakeFolder.syncOnce()); disconnect(connection); QCOMPARE(nGET, 0); // no new download, just a metadata update! @@ -378,6 +367,65 @@ private slots: QVERIFY(fakeFolder.uploadState().children.first().name != chunkingId); } + // Check what happens when the connection is dropped on the PUT (non-chunking) or MOVE (chunking) + // for on the issue #5106 + void connectionDroppedBeforeEtagRecieved_data() + { + QTest::addColumn<bool>("chunking"); + QTest::newRow("big file") << true; + QTest::newRow("small file") << false; + } + void connectionDroppedBeforeEtagRecieved() + { + QFETCH(bool, chunking); + FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; + fakeFolder.syncEngine().account()->setCapabilities({ { "dav", QVariantMap{ { "chunking", "1.0" } } }, { "checksums", QVariantMap{ { "supportedTypes", QStringList() << "SHA1" } } } }); + const int size = chunking ? 150 * 1000 * 1000 : 300; + + // Make the MOVE never reply, but trigger a client-abort and apply the change remotely + QByteArray checksumHeader; + int nGET = 0; + QScopedValueRollback<int> setHttpTimeout(AbstractNetworkJob::httpTimeout, 1); + int responseDelay = AbstractNetworkJob::httpTimeout * 1000 * 1000; // much bigger than http timeout (so a timeout will occur) + // This will perform the operation on the server, but the reply will not come to the client + fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *outgoingData) -> QNetworkReply * { + if (!chunking && op == QNetworkAccessManager::PutOperation) { + checksumHeader = request.rawHeader("OC-Checksum"); + return new DelayedReply<FakePutReply>(responseDelay, fakeFolder.remoteModifier(), op, request, outgoingData->readAll(), &fakeFolder.syncEngine()); + } else if (chunking && request.attribute(QNetworkRequest::CustomVerbAttribute) == "MOVE") { + checksumHeader = request.rawHeader("OC-Checksum"); + return new DelayedReply<FakeChunkMoveReply>(responseDelay, fakeFolder.uploadState(), fakeFolder.remoteModifier(), op, request, &fakeFolder.syncEngine()); + } else if (op == QNetworkAccessManager::GetOperation) { + nGET++; + } + return nullptr; + }); + + + // Test 1: a NEW file + fakeFolder.localModifier().insert("A/a0", size); + QVERIFY(!fakeFolder.syncOnce()); // timeout! + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); // but the upload succeeded + QVERIFY(!checksumHeader.isEmpty()); + fakeFolder.remoteModifier().find("A/a0")->checksums = checksumHeader; // The test system don't do that automatically + // Should be resolved properly + QVERIFY(fakeFolder.syncOnce()); + QCOMPARE(nGET, 0); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + // Test 2: Modify the file further + fakeFolder.localModifier().appendByte("A/a0"); + QVERIFY(!fakeFolder.syncOnce()); // timeout! + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); // but the upload succeeded + fakeFolder.remoteModifier().find("A/a0")->checksums = checksumHeader; + // modify again, should not cause conflict + fakeFolder.localModifier().appendByte("A/a0"); + QVERIFY(!fakeFolder.syncOnce()); // now it's trying to upload the modified file + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + fakeFolder.remoteModifier().find("A/a0")->checksums = checksumHeader; + QVERIFY(fakeFolder.syncOnce()); + QCOMPARE(nGET, 0); + } }; QTEST_GUILESS_MAIN(TestChunkingNG) |