blob: cd8b9794f62f382817d4edbb411a565d150c7105 [file] [log] [blame]
// 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.
#include "RenderChannelImpl.h"
#include <algorithm>
#include <utility>
#include <assert.h>
#include <string.h>
namespace emugl {
RenderChannelImpl::RenderChannelImpl(std::shared_ptr<RendererImpl> renderer)
: mRenderer(std::move(renderer)), mState(State::Empty) {
assert(mState.is_lock_free()); // rethink it if this fails - we've already
// got a lock, use it instead.
}
void RenderChannelImpl::setEventCallback(
RenderChannelImpl::EventCallback callback) {
mOnEvent = std::move(callback);
// Reset the current state to make sure the new subscriber gets it.
mState.store(State::Empty, std::memory_order_release);
onEvent(true);
}
bool RenderChannelImpl::write(ChannelBuffer&& buffer) {
const bool res = mFromGuest.send(std::move(buffer));
onEvent(true);
return res;
}
bool RenderChannelImpl::read(ChannelBuffer* buffer, CallType type) {
if (type == CallType::Nonblocking && mToGuest.size() == 0) {
return false;
}
const bool res = mToGuest.receive(buffer);
onEvent(true);
return res;
}
void RenderChannelImpl::stop() {
stop(true);
}
void RenderChannelImpl::forceStop() {
stop(false);
}
void RenderChannelImpl::stop(bool byGuest) {
if (mStopped) {
return;
}
mStopped = true;
mFromGuest.stop();
mToGuest.stop();
onEvent(byGuest);
}
bool RenderChannelImpl::isStopped() const {
return mStopped;
}
void RenderChannelImpl::writeToGuest(ChannelBuffer&& buf) {
mToGuest.send(std::move(buf));
onEvent(false);
}
size_t RenderChannelImpl::readFromGuest(ChannelBuffer::value_type* buf,
size_t size,
bool blocking) {
assert(buf);
size_t read = 0;
const auto bufEnd = buf + size;
while (buf != bufEnd && !mStopped) {
if (mFromGuestBufferLeft == 0) {
if (mFromGuest.size() == 0 && (read > 0 || !blocking)) {
break;
}
if (!mFromGuest.receive(&mFromGuestBuffer)) {
break;
}
mFromGuestBufferLeft = mFromGuestBuffer.size();
}
const size_t curSize =
std::min<size_t>(bufEnd - buf, mFromGuestBufferLeft);
memcpy(buf, mFromGuestBuffer.data() +
(mFromGuestBuffer.size() - mFromGuestBufferLeft),
curSize);
read += curSize;
buf += curSize;
mFromGuestBufferLeft -= curSize;
}
onEvent(false);
return read;
}
void RenderChannelImpl::onEvent(bool byGuest) {
if (!mOnEvent) {
return;
}
// We need to make sure that the state returned from calcState() remains
// valid when we write it into mState; this means we need to lock both
// calcState() and mState assignment with the same lock - otherwise it is
// possible (and it happened before the lock was added) that some other
// thread would overwrite a newer state with its older value, e.g.:
// - thread 1 reads the last message from |mToGuest|
// - thread 1 enters onEvent()
// - thread 1 calculates state 1 = "can't read", switches out
// - thread 2 writes some data into |mToGuest|
// - thread 2 enters onEvent()
// - thread 2 calculates state 2 = "can read"
// - thread 2 gets the lock
// - thread 2 updates |mState| to "can read", unlocks, switches out
// - thread 1 gets the lock
// - thread 1 overwrites |mState| with state 1 = "can't read"
// - thread 1 unlocks and calls the |mOnEvent| callback.
//
// The result is that state 2 = "can read" is completely lost - callback
// is never called when |mState| is "can read".
//
// But if the whole block of code is locked, threads can't overwrite newer
// |mState| with some older value, and the described situation would never
// happen.
android::base::AutoLock lock(mStateLock);
const State newState = calcState();
if (mState != newState) {
mState = newState;
lock.unlock();
mOnEvent(newState,
byGuest ? EventSource::Client : EventSource::RenderChannel);
}
}
RenderChannel::State RenderChannelImpl::calcState() const {
State state = State::Empty;
if (mStopped) {
state = State::Stopped;
} else {
if (mFromGuest.size() < mFromGuest.capacity()) {
state |= State::CanWrite;
}
if (mToGuest.size() > 0) {
state |= State::CanRead;
}
}
return state;
}
} // namespace emugl