Avoid mutual ownership in qstdweb's File::stream
The mutual ownership of chunkCompleted<->fileReader caused both not to be freed, which resulted in a memory leak. Resolve this by introducing the ChunkedFileReader class which owns itself until file read is finished. Also, resolve a similar issue in qwasmlocalfileaccess. Fixes: QTBUG-109436 Pick-to: 6.5 Change-Id: Ieec4cde15a893fa6a2e21a62d3bb6637374c5364 Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
This commit is contained in:
parent
8420d3e0b2
commit
cc60d70699
@ -34,6 +34,53 @@ static void usePotentialyUnusedSymbols()
|
|||||||
Q_CONSTRUCTOR_FUNCTION(usePotentialyUnusedSymbols)
|
Q_CONSTRUCTOR_FUNCTION(usePotentialyUnusedSymbols)
|
||||||
typedef double uint53_t; // see Number.MAX_SAFE_INTEGER
|
typedef double uint53_t; // see Number.MAX_SAFE_INTEGER
|
||||||
namespace {
|
namespace {
|
||||||
|
// Reads file in chunks in order to avoid holding two copies in memory at the same time
|
||||||
|
struct ChunkedFileReader
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static void read(File file, char *buffer, uint32_t offset, uint32_t end,
|
||||||
|
std::function<void()> onCompleted)
|
||||||
|
{
|
||||||
|
(new ChunkedFileReader(end, std::move(onCompleted), std::move(file)))
|
||||||
|
->readNextChunk(offset, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
ChunkedFileReader(uint32_t end, std::function<void()> onCompleted, File file)
|
||||||
|
: end(end), onCompleted(std::move(onCompleted)), file(std::move(file))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void readNextChunk(uint32_t chunkBegin, char *chunkBuffer)
|
||||||
|
{
|
||||||
|
// Copy current chunk from JS memory to Wasm memory
|
||||||
|
qstdweb::ArrayBuffer result = fileReader.result();
|
||||||
|
qstdweb::Uint8Array(result).copyTo(chunkBuffer);
|
||||||
|
|
||||||
|
// Read next chunk if not at buffer end
|
||||||
|
const uint32_t nextChunkBegin = std::min(chunkBegin + result.byteLength(), end);
|
||||||
|
if (nextChunkBegin == end) {
|
||||||
|
onCompleted();
|
||||||
|
delete this;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
char *nextChunkBuffer = chunkBuffer + result.byteLength();
|
||||||
|
fileReader.onLoad([this, nextChunkBegin, nextChunkBuffer](emscripten::val) {
|
||||||
|
readNextChunk(nextChunkBegin, nextChunkBuffer);
|
||||||
|
});
|
||||||
|
const uint32_t nextChunkEnd = std::min(nextChunkBegin + chunkSize, end);
|
||||||
|
qstdweb::Blob blob = file.slice(nextChunkBegin, nextChunkEnd);
|
||||||
|
fileReader.readAsArrayBuffer(blob);
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr uint32_t chunkSize = 256 * 1024;
|
||||||
|
|
||||||
|
qstdweb::FileReader fileReader;
|
||||||
|
uint32_t end;
|
||||||
|
std::function<void()> onCompleted;
|
||||||
|
File file;
|
||||||
|
};
|
||||||
|
|
||||||
enum class CallbackType {
|
enum class CallbackType {
|
||||||
Then,
|
Then,
|
||||||
Catch,
|
Catch,
|
||||||
@ -403,47 +450,17 @@ std::string Blob::type() const
|
|||||||
|
|
||||||
// Streams partial file content into the given buffer asynchronously. The completed
|
// Streams partial file content into the given buffer asynchronously. The completed
|
||||||
// callback is called on completion.
|
// callback is called on completion.
|
||||||
void File::stream(uint32_t offset, uint32_t length, char *buffer, const std::function<void ()> &completed) const
|
void File::stream(uint32_t offset, uint32_t length, char *buffer,
|
||||||
|
std::function<void()> completed) const
|
||||||
{
|
{
|
||||||
// Read file in chunks in order to avoid holding two copies in memory at the same time
|
ChunkedFileReader::read(*this, buffer, offset, offset + length, std::move(completed));
|
||||||
const uint32_t chunkSize = 256 * 1024;
|
|
||||||
const uint32_t end = offset + length;
|
|
||||||
// assert end < file.size
|
|
||||||
auto fileReader = std::make_shared<qstdweb::FileReader>();
|
|
||||||
|
|
||||||
// "this" is valid now, but may not be by the time the chunkCompleted callback
|
|
||||||
// below is made. Make a copy of the file handle.
|
|
||||||
const File fileHandle = *this;
|
|
||||||
auto chunkCompleted = std::make_shared<std::function<void (uint32_t, char *buffer)>>();
|
|
||||||
*chunkCompleted = [=](uint32_t chunkBegin, char *chunkBuffer) mutable {
|
|
||||||
|
|
||||||
// Copy current chunk from JS memory to Wasm memory
|
|
||||||
qstdweb::ArrayBuffer result = fileReader->result();
|
|
||||||
qstdweb::Uint8Array(result).copyTo(chunkBuffer);
|
|
||||||
|
|
||||||
// Read next chunk if not at buffer end
|
|
||||||
uint32_t nextChunkBegin = std::min(chunkBegin + result.byteLength(), end);
|
|
||||||
uint32_t nextChunkEnd = std::min(nextChunkBegin + chunkSize, end);
|
|
||||||
if (nextChunkBegin == end) {
|
|
||||||
completed();
|
|
||||||
chunkCompleted.reset();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
char *nextChunkBuffer = chunkBuffer + result.byteLength();
|
|
||||||
fileReader->onLoad([=](emscripten::val) { (*chunkCompleted)(nextChunkBegin, nextChunkBuffer); });
|
|
||||||
qstdweb::Blob blob = fileHandle.slice(nextChunkBegin, nextChunkEnd);
|
|
||||||
fileReader->readAsArrayBuffer(blob);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Read first chunk. First iteration is a dummy iteration with no available data.
|
|
||||||
(*chunkCompleted)(offset, buffer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Streams file content into the given buffer asynchronously. The completed
|
// Streams file content into the given buffer asynchronously. The completed
|
||||||
// callback is called on completion.
|
// callback is called on completion.
|
||||||
void File::stream(char *buffer, const std::function<void ()> &completed) const
|
void File::stream(char *buffer, std::function<void()> completed) const
|
||||||
{
|
{
|
||||||
stream(0, size(), buffer, completed);
|
stream(0, size(), buffer, std::move(completed));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string File::type() const
|
std::string File::type() const
|
||||||
|
@ -75,8 +75,9 @@ namespace qstdweb {
|
|||||||
std::string name() const;
|
std::string name() const;
|
||||||
uint64_t size() const;
|
uint64_t size() const;
|
||||||
std::string type() const;
|
std::string type() const;
|
||||||
void stream(uint32_t offset, uint32_t length, char *buffer, const std::function<void ()> &completed) const;
|
void stream(uint32_t offset, uint32_t length, char *buffer,
|
||||||
void stream(char *buffer, const std::function<void ()> &completed) const;
|
std::function<void()> completed) const;
|
||||||
|
void stream(char *buffer, std::function<void()> completed) const;
|
||||||
emscripten::val val();
|
emscripten::val val();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -124,7 +124,7 @@ void readFiles(const qstdweb::FileList &fileList,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read file data into caller-provided buffer
|
// Read file data into caller-provided buffer
|
||||||
file.stream(buffer, [=]() {
|
file.stream(buffer, [readFile = readFile.get(), fileIndex, fileDataReady]() {
|
||||||
fileDataReady();
|
fileDataReady();
|
||||||
(*readFile)(fileIndex + 1);
|
(*readFile)(fileIndex + 1);
|
||||||
});
|
});
|
||||||
|
@ -109,9 +109,9 @@ static void dropEvent(val event)
|
|||||||
QByteArray fileContent;
|
QByteArray fileContent;
|
||||||
fileContent.resize(file.size());
|
fileContent.resize(file.size());
|
||||||
|
|
||||||
file.stream(fileContent.data(), [=]() {
|
file.stream(fileContent.data(), [mimeFormat, fileContent]() {
|
||||||
if (!fileContent.isEmpty()) {
|
if (!fileContent.isEmpty()) {
|
||||||
|
QWasmDrag *wasmDrag = static_cast<QWasmDrag *>(QWasmIntegration::get()->drag());
|
||||||
if (mimeFormat.contains("image")) {
|
if (mimeFormat.contains("image")) {
|
||||||
QImage image;
|
QImage image;
|
||||||
image.loadFromData(fileContent, nullptr);
|
image.loadFromData(fileContent, nullptr);
|
||||||
@ -119,7 +119,7 @@ static void dropEvent(val event)
|
|||||||
} else {
|
} else {
|
||||||
wasmDrag->m_mimeData->setData(mimeFormat, fileContent.data());
|
wasmDrag->m_mimeData->setData(mimeFormat, fileContent.data());
|
||||||
}
|
}
|
||||||
wasmDrag->qWasmDrop();
|
wasmDrag->qWasmDrop();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user