You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
266 lines
8.9 KiB
266 lines
8.9 KiB
// Copyright (C) 2016 The Android Open Source Project
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
#pragma once
|
|
|
|
#include "base/Compiler.h"
|
|
#include "base/Stream.h"
|
|
#include "base/StreamSerializing.h"
|
|
#include "base/ConditionVariable.h"
|
|
#include "base/Lock.h"
|
|
|
|
#include <iterator>
|
|
#include <vector>
|
|
#include <utility>
|
|
|
|
#include <assert.h>
|
|
#include <stddef.h>
|
|
|
|
namespace android {
|
|
namespace base {
|
|
|
|
// Values corresponding to the result of BufferQueue operations.
|
|
// |Ok| means everything went well.
|
|
// |TryAgain| means the operation could not be performed and should be
|
|
// tried later.
|
|
// |Error| means an error happened (i.e. the BufferQueue is closed).
|
|
// |Timeout| means that an item could not be popped in time.
|
|
enum class BufferQueueResult {
|
|
Ok = 0,
|
|
TryAgain = 1,
|
|
Error = 2,
|
|
Timeout = 3,
|
|
};
|
|
|
|
// BufferQueue models a FIFO queue of <T> instances
|
|
// that can be used between two different threads. Note that it depends,
|
|
// for synchronization, on an external lock (passed as a reference in
|
|
// the BufferQueue constructor).
|
|
//
|
|
// This allows one to use multiple BufferQueue instances whose content
|
|
// are protected by a single lock.
|
|
template <class T>
|
|
class BufferQueue {
|
|
using ConditionVariable = android::base::ConditionVariable;
|
|
using Lock = android::base::Lock;
|
|
using AutoLock = android::base::AutoLock;
|
|
|
|
public:
|
|
using value_type = T;
|
|
|
|
// Constructor. |capacity| is the maximum number of T instances in
|
|
// the queue, and |lock| is a reference to an external lock provided by
|
|
// the caller.
|
|
BufferQueue(int capacity, android::base::Lock& lock)
|
|
: mBuffers(capacity), mLock(lock) {}
|
|
|
|
// Return true iff one can send a buffer to the queue, i.e. if it
|
|
// is not full or it would grow anyway.
|
|
bool canPushLocked() const { return !mClosed && mCount < (int)mBuffers.size(); }
|
|
|
|
// Return true iff one can receive a buffer from the queue, i.e. if
|
|
// it is not empty.
|
|
bool canPopLocked() const { return mCount > 0; }
|
|
|
|
// Return true iff the queue is closed.
|
|
bool isClosedLocked() const { return mClosed; }
|
|
|
|
// Changes the operation mode to snapshot or back. In snapshot mode
|
|
// BufferQueue accepts all write requests and accumulates the data, but
|
|
// returns error on all reads.
|
|
void setSnapshotModeLocked(bool on) {
|
|
mSnapshotMode = on;
|
|
if (on && !mClosed) {
|
|
wakeAllWaiters();
|
|
}
|
|
}
|
|
|
|
// Try to send a buffer to the queue. On success, return BufferQueueResult::Ok
|
|
// and moves |buffer| to the queue. On failure, return
|
|
// BufferQueueResult::TryAgain if the queue was full, or BufferQueueResult::Error
|
|
// if it was closed.
|
|
// Note: in snapshot mode it never returns TryAgain, but grows the max
|
|
// queue size instead.
|
|
BufferQueueResult tryPushLocked(T&& buffer) {
|
|
if (mClosed) {
|
|
return BufferQueueResult::Error;
|
|
}
|
|
if (mCount >= (int)mBuffers.size()) {
|
|
if (mSnapshotMode) {
|
|
grow();
|
|
} else {
|
|
return BufferQueueResult::TryAgain;
|
|
}
|
|
}
|
|
int pos = mPos + mCount;
|
|
if (pos >= (int)mBuffers.size()) {
|
|
pos -= mBuffers.size();
|
|
}
|
|
mBuffers[pos] = std::move(buffer);
|
|
if (mCount++ == 0) {
|
|
mCanPop.signal();
|
|
}
|
|
return BufferQueueResult::Ok;
|
|
}
|
|
|
|
// Push a buffer to the queue. This is a blocking call. On success,
|
|
// move |buffer| into the queue and return BufferQueueResult::Ok. On failure,
|
|
// return BufferQueueResult::Error meaning the queue was closed.
|
|
BufferQueueResult pushLocked(T&& buffer) {
|
|
while (mCount == (int)mBuffers.size() && !mSnapshotMode) {
|
|
if (mClosed) {
|
|
return BufferQueueResult::Error;
|
|
}
|
|
mCanPush.wait(&mLock);
|
|
}
|
|
return tryPushLocked(std::move(buffer));
|
|
}
|
|
|
|
// Try to read a buffer from the queue. On success, moves item into
|
|
// |*buffer| and return BufferQueueResult::Ok. On failure, return BufferQueueResult::Error
|
|
// if the queue is empty and closed or in snapshot mode, and
|
|
// BufferQueueResult::TryAgain if it is empty but not closed.
|
|
BufferQueueResult tryPopLocked(T* buffer) {
|
|
if (mCount == 0) {
|
|
return (mClosed || mSnapshotMode) ? BufferQueueResult::Error
|
|
: BufferQueueResult::TryAgain;
|
|
}
|
|
*buffer = std::move(mBuffers[mPos]);
|
|
int pos = mPos + 1;
|
|
if (pos >= (int)mBuffers.size()) {
|
|
pos -= mBuffers.size();
|
|
}
|
|
mPos = pos;
|
|
if (mCount-- == (int)mBuffers.size()) {
|
|
mCanPush.signal();
|
|
}
|
|
return BufferQueueResult::Ok;
|
|
}
|
|
|
|
// Pop a buffer from the queue. This is a blocking call. On success,
|
|
// move item into |*buffer| and return BufferQueueResult::Ok. On failure,
|
|
// return BufferQueueResult::Error to indicate the queue was closed or is in
|
|
// snapshot mode.
|
|
BufferQueueResult popLocked(T* buffer) {
|
|
while (mCount == 0 && !mSnapshotMode) {
|
|
if (mClosed) {
|
|
// Closed queue is empty.
|
|
return BufferQueueResult::Error;
|
|
}
|
|
mCanPop.wait(&mLock);
|
|
}
|
|
return tryPopLocked(buffer);
|
|
}
|
|
|
|
// Pop a buffer from the queue. This is a blocking call. On success,
|
|
// move item into |*buffer| and return BufferQueueResult::Ok. On failure,
|
|
// return BufferQueueResult::Error to indicate the queue was closed or is in
|
|
// snapshot mode. Returns BufferQueueResult::Timeout if we waited passed
|
|
// waitUntilUs.
|
|
BufferQueueResult popLockedBefore(T* buffer, uint64_t waitUntilUs) {
|
|
while (mCount == 0 && !mSnapshotMode) {
|
|
if (mClosed) {
|
|
// Closed queue is empty.
|
|
return BufferQueueResult::Error;
|
|
}
|
|
if (!mCanPop.timedWait(&mLock, waitUntilUs)) {
|
|
return BufferQueueResult::Timeout;
|
|
}
|
|
|
|
}
|
|
return tryPopLocked(buffer);
|
|
}
|
|
|
|
// Close the queue, it is no longer possible to push new items
|
|
// to it (i.e. push() will always return BufferQueueResult::Error), or to
|
|
// read from an empty queue (i.e. pop() will always return
|
|
// BufferQueueResult::Error once the queue becomes empty).
|
|
void closeLocked() {
|
|
mClosed = true;
|
|
wakeAllWaiters();
|
|
}
|
|
|
|
// Save to a snapshot file
|
|
void onSaveLocked(android::base::Stream* stream) {
|
|
stream->putByte(mClosed);
|
|
if (!mClosed) {
|
|
stream->putBe32(mCount);
|
|
for (int i = 0; i < mCount; i++) {
|
|
android::base::saveBuffer(
|
|
stream, mBuffers[(i + mPos) % mBuffers.size()]);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool onLoadLocked(android::base::Stream* stream) {
|
|
mClosed = stream->getByte();
|
|
if (!mClosed) {
|
|
mCount = stream->getBe32();
|
|
if ((int)mBuffers.size() < mCount) {
|
|
mBuffers.resize(mCount);
|
|
}
|
|
mPos = 0;
|
|
for (int i = 0; i < mCount; i++) {
|
|
if (!android::base::loadBuffer(stream, &mBuffers[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
void grow() {
|
|
assert(mCount == (int)mBuffers.size());
|
|
std::vector<T> newBuffers;
|
|
newBuffers.reserve(mBuffers.size() * 2);
|
|
newBuffers.insert(newBuffers.end(),
|
|
std::make_move_iterator(mBuffers.begin() + mPos),
|
|
std::make_move_iterator(
|
|
mBuffers.begin() +
|
|
std::min<int>(mPos + mCount, mBuffers.size())));
|
|
newBuffers.insert(
|
|
newBuffers.end(), std::make_move_iterator(mBuffers.begin()),
|
|
std::make_move_iterator(mBuffers.begin() +
|
|
(mPos + mCount) % mBuffers.size()));
|
|
mBuffers = std::move(newBuffers);
|
|
mBuffers.resize(mBuffers.capacity());
|
|
mPos = 0;
|
|
}
|
|
|
|
void wakeAllWaiters() {
|
|
if (mCount == (int)mBuffers.size()) {
|
|
mCanPush.broadcast();
|
|
}
|
|
if (mCount == 0) {
|
|
mCanPop.broadcast();
|
|
}
|
|
}
|
|
|
|
private:
|
|
int mPos = 0;
|
|
int mCount = 0;
|
|
bool mClosed = false;
|
|
bool mSnapshotMode = false;
|
|
std::vector<T> mBuffers;
|
|
|
|
Lock& mLock;
|
|
ConditionVariable mCanPush;
|
|
ConditionVariable mCanPop;
|
|
|
|
DISALLOW_COPY_ASSIGN_AND_MOVE(BufferQueue);
|
|
};
|
|
|
|
} // namespace base
|
|
} // namespace android
|