Shaka Packager SDK
Loading...
Searching...
No Matches
threaded_io_file.cc
1// Copyright 2015 Google LLC. All rights reserved.
2//
3// Use of this source code is governed by a BSD-style
4// license that can be found in the LICENSE file or at
5// https://developers.google.com/open-source/licenses/bsd
6
7#include <packager/file/threaded_io_file.h>
8
9#include <atomic>
10#include <cstdint>
11#include <functional>
12#include <memory>
13#include <utility>
14
15#include <absl/log/check.h>
16#include <absl/log/log.h>
17#include <absl/synchronization/mutex.h>
18
19#include <packager/file.h>
20#include <packager/file/file_closer.h>
21#include <packager/file/thread_pool.h>
22
23namespace shaka {
24
25ThreadedIoFile::ThreadedIoFile(std::unique_ptr<File, FileCloser> internal_file,
26 Mode mode,
27 uint64_t io_cache_size,
28 uint64_t io_block_size)
29 : File(internal_file->file_name()),
30 internal_file_(std::move(internal_file)),
31 mode_(mode),
32 cache_(io_cache_size),
33 io_buffer_(io_block_size),
34 position_(0),
35 size_(0),
36 eof_(false),
37 internal_file_error_(0),
38 flushing_(false),
39 flush_complete_(false),
40 task_exited_(false) {
41 DCHECK(internal_file_);
42}
43
44ThreadedIoFile::~ThreadedIoFile() {}
45
46bool ThreadedIoFile::Open() {
47 DCHECK(internal_file_);
48
49 if (!internal_file_->Open())
50 return false;
51
52 position_ = 0;
53 size_ = internal_file_->Size();
54
55 ThreadPool::instance.PostTask(std::bind(&ThreadedIoFile::TaskHandler, this));
56 return true;
57}
58
59bool ThreadedIoFile::Close() {
60 DCHECK(internal_file_);
61
62 bool result = true;
63 if (mode_ == kOutputMode)
64 result = Flush();
65
66 cache_.Close();
67 WaitForSignal(&task_exited_mutex_, &task_exited_);
68
69 result &= internal_file_.release()->Close();
70 delete this;
71 return result;
72}
73
74int64_t ThreadedIoFile::Read(void* buffer, uint64_t length) {
75 DCHECK(internal_file_);
76 DCHECK_EQ(kInputMode, mode_);
77
78 if (eof_.load(std::memory_order_relaxed) && !cache_.BytesCached())
79 return 0;
80
81 if (internal_file_error_.load(std::memory_order_relaxed))
82 return internal_file_error_.load(std::memory_order_relaxed);
83
84 uint64_t bytes_read = cache_.Read(buffer, length);
85 position_ += bytes_read;
86
87 return bytes_read;
88}
89
90int64_t ThreadedIoFile::Write(const void* buffer, uint64_t length) {
91 DCHECK(internal_file_);
92 DCHECK_EQ(kOutputMode, mode_);
93
94 if (internal_file_error_.load(std::memory_order_relaxed))
95 return internal_file_error_.load(std::memory_order_relaxed);
96
97 uint64_t bytes_written = cache_.Write(buffer, length);
98 position_ += bytes_written;
99 if (position_ > size_)
100 size_ = position_;
101
102 return bytes_written;
103}
104
105void ThreadedIoFile::CloseForWriting() {}
106
107int64_t ThreadedIoFile::Size() {
108 DCHECK(internal_file_);
109
110 return size_;
111}
112
113bool ThreadedIoFile::Flush() {
114 DCHECK(internal_file_);
115 DCHECK_EQ(kOutputMode, mode_);
116
117 if (internal_file_error_.load(std::memory_order_relaxed))
118 return false;
119
120 {
121 absl::MutexLock lock(flush_mutex_);
122 flushing_ = true;
123 flush_complete_ = false;
124 }
125 cache_.Close();
126
127 WaitForSignal(&flush_mutex_, &flush_complete_);
128
129 return internal_file_->Flush();
130}
131
132bool ThreadedIoFile::Seek(uint64_t position) {
133 if (mode_ == kOutputMode) {
134 // Writing. Just flush the cache and seek.
135 if (!Flush())
136 return false;
137 if (!internal_file_->Seek(position))
138 return false;
139 } else {
140 // Reading. Close cache, wait for thread task to exit, seek, and re-post
141 // the task.
142 cache_.Close();
143 WaitForSignal(&task_exited_mutex_, &task_exited_);
144
145 bool result = internal_file_->Seek(position);
146 if (!result) {
147 // Seek failed. Seek to logical position instead.
148 if (!internal_file_->Seek(position_) && (position != position_)) {
149 LOG(WARNING) << "Seek failed. ThreadedIoFile left in invalid state.";
150 }
151 }
152 cache_.Reopen();
153 eof_ = false;
154
155 ThreadPool::instance.PostTask(
156 std::bind(&ThreadedIoFile::TaskHandler, this));
157 if (!result)
158 return false;
159 }
160 position_ = position;
161 return true;
162}
163
164bool ThreadedIoFile::Tell(uint64_t* position) {
165 DCHECK(position);
166
167 *position = position_;
168 return true;
169}
170
171void ThreadedIoFile::TaskHandler() {
172 {
173 absl::MutexLock lock(task_exited_mutex_);
174 task_exited_ = false;
175 }
176
177 if (mode_ == kInputMode)
178 RunInInputMode();
179 else
180 RunInOutputMode();
181
182 {
183 absl::MutexLock lock(task_exited_mutex_);
184 task_exited_ = true;
185 }
186}
187
188void ThreadedIoFile::RunInInputMode() {
189 DCHECK(internal_file_);
190 DCHECK_EQ(kInputMode, mode_);
191
192 while (true) {
193 int64_t read_result =
194 internal_file_->Read(&io_buffer_[0], io_buffer_.size());
195 if (read_result <= 0) {
196 eof_.store(read_result == 0, std::memory_order_relaxed);
197 internal_file_error_.store(read_result, std::memory_order_relaxed);
198 cache_.Close();
199 return;
200 }
201 if (cache_.Write(&io_buffer_[0], read_result) == 0) {
202 return;
203 }
204 }
205}
206
207void ThreadedIoFile::RunInOutputMode() {
208 DCHECK(internal_file_);
209 DCHECK_EQ(kOutputMode, mode_);
210
211 while (true) {
212 uint64_t write_bytes = cache_.Read(&io_buffer_[0], io_buffer_.size());
213 if (write_bytes == 0) {
214 absl::MutexLock lock(flush_mutex_);
215 if (flushing_) {
216 cache_.Reopen();
217 flushing_ = false;
218 flush_complete_ = true;
219 } else {
220 return;
221 }
222 } else {
223 uint64_t bytes_written(0);
224 while (bytes_written < write_bytes) {
225 int64_t write_result = internal_file_->Write(
226 &io_buffer_[bytes_written], write_bytes - bytes_written);
227 if (write_result < 0) {
228 internal_file_error_.store(write_result, std::memory_order_relaxed);
229 cache_.Close();
230
231 absl::MutexLock lock(flush_mutex_);
232 if (flushing_) {
233 flushing_ = false;
234 flush_complete_ = true;
235 }
236 return;
237 }
238 bytes_written += write_result;
239 }
240 }
241 }
242}
243
244void ThreadedIoFile::WaitForSignal(absl::Mutex* mutex, bool* condition) {
245 // This waits until the boolean condition variable is true, then locks the
246 // mutex. The check is done every time the mutex is unlocked. As long as
247 // this mutex is held when the variable is modified, this wait will always
248 // wake up when the variable is changed to true.
249 mutex->LockWhen(absl::Condition(condition));
250
251 // LockWhen leaves the mutex locked. Return after unlocking the mutex again.
252 mutex->unlock();
253}
254
255} // namespace shaka
All the methods that are virtual are virtual for mocking.