Commit 73506a4a authored by mtomasz's avatar mtomasz Committed by Commit bot

[fsp] Make the reading operation abortable.

Reading may be slow. We should be able to abort it to avoid unnecessary
consumption of resources such as network, cpu and memory.

TEST=browser_test: *FileSystemProvider*ReadFile*
BUG=400574

Review URL: https://codereview.chromium.org/496953003

Cr-Commit-Position: refs/heads/master@{#291645}
parent c85a7365
...@@ -30,116 +30,179 @@ void Int64ToIntCompletionCallback(net::CompletionCallback callback, ...@@ -30,116 +30,179 @@ void Int64ToIntCompletionCallback(net::CompletionCallback callback,
callback.Run(static_cast<int>(result)); callback.Run(static_cast<int>(result));
} }
// Opens a file for reading and calls the completion callback. Must be called } // namespace
// on UI thread.
void OpenFileOnUIThread( class FileStreamReader::OperationRunner
const storage::FileSystemURL& url, : public base::RefCountedThreadSafe<FileStreamReader::OperationRunner> {
const FileStreamReader::OpenFileCompletedCallback& callback) { public:
DCHECK_CURRENTLY_ON(BrowserThread::UI); OperationRunner() : file_handle_(-1) {}
util::FileSystemURLParser parser(url); // Opens a file for reading and calls the completion callback. Must be called
if (!parser.Parse()) { // on UI thread.
callback.Run(base::WeakPtr<ProvidedFileSystemInterface>(), void OpenFileOnUIThread(
base::FilePath(), const storage::FileSystemURL& url,
0 /* file_handle */, const storage::AsyncFileUtil::StatusCallback& callback) {
base::File::FILE_ERROR_SECURITY); DCHECK_CURRENTLY_ON(BrowserThread::UI);
return;
util::FileSystemURLParser parser(url);
if (!parser.Parse()) {
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(callback, base::File::FILE_ERROR_SECURITY));
return;
}
file_system_ = parser.file_system()->GetWeakPtr();
file_path_ = parser.file_path();
abort_callback_ = parser.file_system()->OpenFile(
file_path_,
ProvidedFileSystemInterface::OPEN_FILE_MODE_READ,
base::Bind(
&OperationRunner::OnOpenFileCompletedOnUIThread, this, callback));
} }
parser.file_system()->OpenFile( // Closes a file. Ignores result, since it is called from a constructor.
parser.file_path(), // Must be called on UI thread.
ProvidedFileSystemInterface::OPEN_FILE_MODE_READ, void CloseFileOnUIThread() {
base::Bind( DCHECK_CURRENTLY_ON(BrowserThread::UI);
callback, parser.file_system()->GetWeakPtr(), parser.file_path())); if (file_system_.get() && file_handle_ != -1) {
} // Closing a file must not be aborted, since we could end up on files
// which are never closed.
file_system_->CloseFile(file_handle_, base::Bind(&EmptyStatusCallback));
}
}
// Forwards results of calling OpenFileOnUIThread back to the IO thread. // Requests reading contents of a file. In case of either success or a failure
void OnOpenFileCompletedOnUIThread( // |callback| is executed. It can be called many times, until |has_more| is
const FileStreamReader::OpenFileCompletedCallback& callback, // set to false. This function guarantees that it will succeed only if the
base::WeakPtr<ProvidedFileSystemInterface> file_system, // file has not been changed while reading. Must be called on UI thread.
const base::FilePath& file_path, void ReadFileOnUIThread(
int file_handle, scoped_refptr<net::IOBuffer> buffer,
base::File::Error result) { int64 offset,
DCHECK_CURRENTLY_ON(BrowserThread::UI); int length,
BrowserThread::PostTask( const ProvidedFileSystemInterface::ReadChunkReceivedCallback& callback) {
BrowserThread::IO, DCHECK_CURRENTLY_ON(BrowserThread::UI);
FROM_HERE,
base::Bind(callback, file_system, file_path, file_handle, result)); // If the file system got unmounted, then abort the reading operation.
} if (!file_system_.get()) {
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(
callback, 0, false /* has_more */, base::File::FILE_ERROR_ABORT));
return;
}
abort_callback_ = file_system_->ReadFile(
file_handle_,
buffer,
offset,
length,
base::Bind(
&OperationRunner::OnReadFileCompletedOnUIThread, this, callback));
}
// Closes a file. Ignores result, since it is called from a constructor. // Requests metadata of a file. In case of either succes or a failure,
// Must be called on UI thread. // |callback| is executed. Must be called on UI thread.
void CloseFileOnUIThread(base::WeakPtr<ProvidedFileSystemInterface> file_system, void GetMetadataOnUIThread(
int file_handle) { const ProvidedFileSystemInterface::GetMetadataCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI); DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (file_system.get())
file_system->CloseFile(file_handle, base::Bind(&EmptyStatusCallback)); // If the file system got unmounted, then abort the get length operation.
} if (!file_system_.get()) {
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(callback, EntryMetadata(), base::File::FILE_ERROR_ABORT));
return;
}
abort_callback_ = file_system_->GetMetadata(
file_path_,
base::Bind(&OperationRunner::OnGetMetadataCompletedOnUIThread,
this,
callback));
}
// Requests reading contents of a file. In case of either success or a failure // Aborts the most recent operation (if exists), and calls the callback.
// |callback| is executed. It can be called many times, until |has_more| is set void AbortOnUIThread(const storage::AsyncFileUtil::StatusCallback& callback) {
// to false. This function guarantees that it will succeed only if the file has DCHECK_CURRENTLY_ON(BrowserThread::UI);
// not been changed while reading. Must be called on UI thread.
void ReadFileOnUIThread( if (abort_callback_.is_null()) {
base::WeakPtr<ProvidedFileSystemInterface> file_system, // No operation to be cancelled. At most a callback call, which will be
int file_handle, // discarded.
scoped_refptr<net::IOBuffer> buffer, BrowserThread::PostTask(BrowserThread::IO,
int64 offset, FROM_HERE,
int length, base::Bind(callback, base::File::FILE_OK));
const ProvidedFileSystemInterface::ReadChunkReceivedCallback& callback) { return;
DCHECK_CURRENTLY_ON(BrowserThread::UI); }
// If the file system got unmounted, then abort the reading operation. const ProvidedFileSystemInterface::AbortCallback abort_callback =
if (!file_system.get()) { abort_callback_;
callback.Run(0, false /* has_more */, base::File::FILE_ERROR_ABORT); abort_callback_ = ProvidedFileSystemInterface::AbortCallback();
return; abort_callback.Run(base::Bind(
&OperationRunner::OnAbortCompletedOnUIThread, this, callback));
} }
file_system->ReadFile(file_handle, buffer, offset, length, callback); private:
} friend class base::RefCountedThreadSafe<OperationRunner>;
// Forward the completion callback to IO thread. virtual ~OperationRunner() {}
void OnReadChunkReceivedOnUIThread(
const ProvidedFileSystemInterface::ReadChunkReceivedCallback&
chunk_received_callback,
int chunk_length,
bool has_more,
base::File::Error result) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(chunk_received_callback, chunk_length, has_more, result));
}
// Requests metadata of a file. In case of either succes or a failure, // Remembers a file handle for further operations and forwards the result to
// |callback is executed. Must be called on UI thread. // the IO thread.
void GetMetadataOnUIThread( void OnOpenFileCompletedOnUIThread(
base::WeakPtr<ProvidedFileSystemInterface> file_system, const storage::AsyncFileUtil::StatusCallback& callback,
const base::FilePath& file_path, int file_handle,
const ProvidedFileSystemInterface::GetMetadataCallback& callback) { base::File::Error result) {
DCHECK_CURRENTLY_ON(BrowserThread::UI); DCHECK_CURRENTLY_ON(BrowserThread::UI);
// If the file system got unmounted, then abort the get length operation. file_handle_ = file_handle;
if (!file_system.get()) { BrowserThread::PostTask(
callback.Run(EntryMetadata(), base::File::FILE_ERROR_ABORT); BrowserThread::IO, FROM_HERE, base::Bind(callback, result));
return;
} }
file_system->GetMetadata(file_path, callback); // Forwards a metadata to the IO thread.
} void OnGetMetadataCompletedOnUIThread(
const ProvidedFileSystemInterface::GetMetadataCallback& callback,
const EntryMetadata& metadata,
base::File::Error result) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE, base::Bind(callback, metadata, result));
}
// Forward the completion callback to IO thread. // Forwards a response of reading from a file to the IO thread.
void OnGetMetadataReceivedOnUIThread( void OnReadFileCompletedOnUIThread(
const ProvidedFileSystemInterface::GetMetadataCallback& callback, const ProvidedFileSystemInterface::ReadChunkReceivedCallback&
const EntryMetadata& metadata, chunk_received_callback,
base::File::Error result) { int chunk_length,
DCHECK_CURRENTLY_ON(BrowserThread::UI); bool has_more,
BrowserThread::PostTask( base::File::Error result) {
BrowserThread::IO, FROM_HERE, base::Bind(callback, metadata, result)); DCHECK_CURRENTLY_ON(BrowserThread::UI);
} BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(chunk_received_callback, chunk_length, has_more, result));
}
} // namespace // Forwards a response of aborting an operation to the IO thread.
void OnAbortCompletedOnUIThread(
const storage::AsyncFileUtil::StatusCallback& callback,
base::File::Error result) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE, base::Bind(callback, result));
}
ProvidedFileSystemInterface::AbortCallback abort_callback_;
base::WeakPtr<ProvidedFileSystemInterface> file_system_;
base::FilePath file_path_;
int file_handle_;
DISALLOW_COPY_AND_ASSIGN(OperationRunner);
};
FileStreamReader::FileStreamReader(storage::FileSystemContext* context, FileStreamReader::FileStreamReader(storage::FileSystemContext* context,
const storage::FileSystemURL& url, const storage::FileSystemURL& url,
...@@ -149,16 +212,24 @@ FileStreamReader::FileStreamReader(storage::FileSystemContext* context, ...@@ -149,16 +212,24 @@ FileStreamReader::FileStreamReader(storage::FileSystemContext* context,
current_offset_(initial_offset), current_offset_(initial_offset),
current_length_(0), current_length_(0),
expected_modification_time_(expected_modification_time), expected_modification_time_(expected_modification_time),
runner_(new OperationRunner),
state_(NOT_INITIALIZED), state_(NOT_INITIALIZED),
file_handle_(0),
weak_ptr_factory_(this) { weak_ptr_factory_(this) {
} }
FileStreamReader::~FileStreamReader() { FileStreamReader::~FileStreamReader() {
// FileStreamReader doesn't have a Cancel() method like in FileStreamWriter.
// Therefore, aborting is done from the destructor.
BrowserThread::PostTask(BrowserThread::UI,
FROM_HERE,
base::Bind(&OperationRunner::AbortOnUIThread,
runner_,
base::Bind(&EmptyStatusCallback)));
BrowserThread::PostTask( BrowserThread::PostTask(
BrowserThread::UI, BrowserThread::UI,
FROM_HERE, FROM_HERE,
base::Bind(&CloseFileOnUIThread, file_system_, file_handle_)); base::Bind(&OperationRunner::CloseFileOnUIThread, runner_));
} }
void FileStreamReader::Initialize( void FileStreamReader::Initialize(
...@@ -170,21 +241,18 @@ void FileStreamReader::Initialize( ...@@ -170,21 +241,18 @@ void FileStreamReader::Initialize(
BrowserThread::PostTask( BrowserThread::PostTask(
BrowserThread::UI, BrowserThread::UI,
FROM_HERE, FROM_HERE,
base::Bind(&OpenFileOnUIThread, base::Bind(&OperationRunner::OpenFileOnUIThread,
runner_,
url_, url_,
base::Bind(&OnOpenFileCompletedOnUIThread, base::Bind(&FileStreamReader::OnOpenFileCompleted,
base::Bind(&FileStreamReader::OnOpenFileCompleted, weak_ptr_factory_.GetWeakPtr(),
weak_ptr_factory_.GetWeakPtr(), pending_closure,
pending_closure, error_callback)));
error_callback))));
} }
void FileStreamReader::OnOpenFileCompleted( void FileStreamReader::OnOpenFileCompleted(
const base::Closure& pending_closure, const base::Closure& pending_closure,
const net::Int64CompletionCallback& error_callback, const net::Int64CompletionCallback& error_callback,
base::WeakPtr<ProvidedFileSystemInterface> file_system,
const base::FilePath& file_path,
int file_handle,
base::File::Error result) { base::File::Error result) {
DCHECK_CURRENTLY_ON(BrowserThread::IO); DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK_EQ(INITIALIZING, state_); DCHECK_EQ(INITIALIZING, state_);
...@@ -197,23 +265,18 @@ void FileStreamReader::OnOpenFileCompleted( ...@@ -197,23 +265,18 @@ void FileStreamReader::OnOpenFileCompleted(
return; return;
} }
file_system_ = file_system; DCHECK_EQ(base::File::FILE_OK, result);
file_path_ = file_path;
file_handle_ = file_handle;
DCHECK_LT(0, file_handle);
// Verify the last modification time. // Verify the last modification time.
BrowserThread::PostTask( BrowserThread::PostTask(
BrowserThread::UI, BrowserThread::UI,
FROM_HERE, FROM_HERE,
base::Bind(&GetMetadataOnUIThread, base::Bind(&OperationRunner::GetMetadataOnUIThread,
file_system_, runner_,
file_path_, base::Bind(&FileStreamReader::OnInitializeCompleted,
base::Bind(&OnGetMetadataReceivedOnUIThread, weak_ptr_factory_.GetWeakPtr(),
base::Bind(&FileStreamReader::OnInitializeCompleted, pending_closure,
weak_ptr_factory_.GetWeakPtr(), error_callback)));
pending_closure,
error_callback))));
} }
void FileStreamReader::OnInitializeCompleted( void FileStreamReader::OnInitializeCompleted(
...@@ -341,16 +404,14 @@ void FileStreamReader::ReadAfterInitialized( ...@@ -341,16 +404,14 @@ void FileStreamReader::ReadAfterInitialized(
BrowserThread::PostTask( BrowserThread::PostTask(
BrowserThread::UI, BrowserThread::UI,
FROM_HERE, FROM_HERE,
base::Bind(&ReadFileOnUIThread, base::Bind(&OperationRunner::ReadFileOnUIThread,
file_system_, runner_,
file_handle_,
buffer, buffer,
current_offset_, current_offset_,
buffer_length, buffer_length,
base::Bind(&OnReadChunkReceivedOnUIThread, base::Bind(&FileStreamReader::OnReadChunkReceived,
base::Bind(&FileStreamReader::OnReadChunkReceived, weak_ptr_factory_.GetWeakPtr(),
weak_ptr_factory_.GetWeakPtr(), callback)));
callback))));
} }
void FileStreamReader::GetLengthAfterInitialized( void FileStreamReader::GetLengthAfterInitialized(
...@@ -362,14 +423,11 @@ void FileStreamReader::GetLengthAfterInitialized( ...@@ -362,14 +423,11 @@ void FileStreamReader::GetLengthAfterInitialized(
BrowserThread::UI, BrowserThread::UI,
FROM_HERE, FROM_HERE,
base::Bind( base::Bind(
&GetMetadataOnUIThread, &OperationRunner::GetMetadataOnUIThread,
file_system_, runner_,
file_path_, base::Bind(&FileStreamReader::OnGetMetadataForGetLengthReceived,
base::Bind( weak_ptr_factory_.GetWeakPtr(),
&OnGetMetadataReceivedOnUIThread, callback)));
base::Bind(&FileStreamReader::OnGetMetadataForGetLengthReceived,
weak_ptr_factory_.GetWeakPtr(),
callback))));
} }
void FileStreamReader::OnReadChunkReceived( void FileStreamReader::OnReadChunkReceived(
......
...@@ -43,6 +43,11 @@ class FileStreamReader : public storage::FileStreamReader { ...@@ -43,6 +43,11 @@ class FileStreamReader : public storage::FileStreamReader {
const net::Int64CompletionCallback& callback) OVERRIDE; const net::Int64CompletionCallback& callback) OVERRIDE;
private: private:
// Helper class for executing operations on the provided file system. All
// of its methods must be called on UI thread. Callbacks are called on IO
// thread.
class OperationRunner;
// State of the file stream reader. // State of the file stream reader.
enum State { NOT_INITIALIZED, INITIALIZING, INITIALIZED, FAILED }; enum State { NOT_INITIALIZED, INITIALIZING, INITIALIZED, FAILED };
...@@ -59,9 +64,6 @@ class FileStreamReader : public storage::FileStreamReader { ...@@ -59,9 +64,6 @@ class FileStreamReader : public storage::FileStreamReader {
void OnOpenFileCompleted( void OnOpenFileCompleted(
const base::Closure& pending_closure, const base::Closure& pending_closure,
const net::Int64CompletionCallback& error_callback, const net::Int64CompletionCallback& error_callback,
base::WeakPtr<ProvidedFileSystemInterface> file_system,
const base::FilePath& file_path,
int file_handle,
base::File::Error result); base::File::Error result);
// Called when initialization is completed with either a success or an error. // Called when initialization is completed with either a success or an error.
...@@ -98,13 +100,9 @@ class FileStreamReader : public storage::FileStreamReader { ...@@ -98,13 +100,9 @@ class FileStreamReader : public storage::FileStreamReader {
int64 current_offset_; int64 current_offset_;
int64 current_length_; int64 current_length_;
base::Time expected_modification_time_; base::Time expected_modification_time_;
scoped_refptr<OperationRunner> runner_;
State state_; State state_;
// Set during initialization (in case of a success).
base::WeakPtr<ProvidedFileSystemInterface> file_system_;
base::FilePath file_path_;
int file_handle_;
base::WeakPtrFactory<FileStreamReader> weak_ptr_factory_; base::WeakPtrFactory<FileStreamReader> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(FileStreamReader); DISALLOW_COPY_AND_ASSIGN(FileStreamReader);
}; };
......
...@@ -42,6 +42,19 @@ var TESTING_BROKEN_TIRAMISU_FILE = Object.freeze({ ...@@ -42,6 +42,19 @@ var TESTING_BROKEN_TIRAMISU_FILE = Object.freeze({
modificationTime: new Date(2014, 1, 25, 7, 36, 12) modificationTime: new Date(2014, 1, 25, 7, 36, 12)
}); });
/**
* Metadata of a broken file used to read contents from, but it simulates
* a very long read, in order to verify the aborting mechanism.
* @type {Object}
* @const
*/
var TESTING_VANILLA_FOR_ABORT_FILE = Object.freeze({
isDirectory: false,
name: 'vanilla.txt',
size: TESTING_TEXT.length,
modificationTime: new Date(2014, 1, 25, 7, 36, 12)
});
/** /**
* Requests reading contents of a file, previously opened with <code> * Requests reading contents of a file, previously opened with <code>
* openRequestId</code>. * openRequestId</code>.
...@@ -77,6 +90,11 @@ function onReadFileRequested(options, onSuccess, onError) { ...@@ -77,6 +90,11 @@ function onReadFileRequested(options, onSuccess, onError) {
return; return;
} }
if (filePath == '/' + TESTING_VANILLA_FOR_ABORT_FILE.name) {
// Do nothing. This simulates a very slow read.
return;
}
if (filePath == '/' + TESTING_BROKEN_TIRAMISU_FILE.name) { if (filePath == '/' + TESTING_BROKEN_TIRAMISU_FILE.name) {
onError('ACCESS_DENIED'); // enum ProviderError. onError('ACCESS_DENIED'); // enum ProviderError.
return; return;
...@@ -103,6 +121,8 @@ function setUp(callback) { ...@@ -103,6 +121,8 @@ function setUp(callback) {
TESTING_TIRAMISU_FILE; TESTING_TIRAMISU_FILE;
test_util.defaultMetadata['/' + TESTING_BROKEN_TIRAMISU_FILE.name] = test_util.defaultMetadata['/' + TESTING_BROKEN_TIRAMISU_FILE.name] =
TESTING_BROKEN_TIRAMISU_FILE; TESTING_BROKEN_TIRAMISU_FILE;
test_util.defaultMetadata['/' + TESTING_VANILLA_FOR_ABORT_FILE.name] =
TESTING_VANILLA_FOR_ABORT_FILE;
chrome.fileSystemProvider.onReadFileRequested.addListener( chrome.fileSystemProvider.onReadFileRequested.addListener(
onReadFileRequested); onReadFileRequested);
...@@ -143,7 +163,8 @@ function runTests() { ...@@ -143,7 +163,8 @@ function runTests() {
chrome.test.fail(error.name); chrome.test.fail(error.name);
}); });
}, },
// Read contents of a file file, but with an error on the way. This should
// Read contents of a file, but with an error on the way. This should
// result in an error. // result in an error.
function readEntriesError() { function readEntriesError() {
var onTestSuccess = chrome.test.callbackPass(); var onTestSuccess = chrome.test.callbackPass();
...@@ -163,7 +184,56 @@ function runTests() { ...@@ -163,7 +184,56 @@ function runTests() {
fileReader.readAsText(file); fileReader.readAsText(file);
}, },
function(error) { function(error) {
chrome.test.fail(); chrome.test.fail(error.name);
});
},
function(error) {
chrome.test.fail(error.name);
});
},
// Abort reading a file with a registered abort handler. Should result in a
// gracefully terminated reading operation.
function abortReadingSuccess() {
var onTestSuccess = chrome.test.callbackPass();
var onAbortRequested = function(options, onSuccess, onError) {
chrome.fileSystemProvider.onAbortRequested.removeListener(
onAbortRequested);
onSuccess();
onTestSuccess();
};
chrome.fileSystemProvider.onAbortRequested.addListener(
onAbortRequested);
test_util.fileSystem.root.getFile(
TESTING_VANILLA_FOR_ABORT_FILE.name,
{create: false, exclusive: false},
function(fileEntry) {
fileEntry.file(function(file) {
var hadAbort = false;
var fileReader = new FileReader();
fileReader.onload = function(e) {
if (!hadAbort) {
chrome.test.fail(
'Unexpectedly finished writing, despite aborting.');
return;
}
chrome.test.fail();
};
fileReader.onerror = function(e) {
chrome.test.assertEq(
'AbortError', fileReader.error.name);
};
fileReader.readAsText(file);
setTimeout(function() {
// Abort the operation after it's started.
fileReader.abort();
}, 0);
},
function(error) {
chrome.test.fail(error.name);
}); });
}, },
function(error) { function(error) {
......
...@@ -316,7 +316,7 @@ function runTests() { ...@@ -316,7 +316,7 @@ function runTests() {
}, },
// Abort writing to a valid file with a registered abort handler. Should // Abort writing to a valid file with a registered abort handler. Should
// resurt in a gracefully terminated writing operation. // result in a gracefully terminated writing operation.
function abortWritingSuccess() { function abortWritingSuccess() {
var onTestSuccess = chrome.test.callbackPass(); var onTestSuccess = chrome.test.callbackPass();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment