/*
Copyright (C) 2009-2016 Red Hat, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, see .
*/
#include
#include "red-channel.h"
#include "red-client.h"
#include "reds.h"
#define FOREACH_CHANNEL_CLIENT(_client, _data) \
GLIST_FOREACH(_client->channels, RedChannelClient, _data)
RedClient::~RedClient()
{
if (mcc) {
mcc->unref();
mcc = nullptr;
}
spice_debug("release client=%p", this);
pthread_mutex_destroy(&lock);
}
RedClient::RedClient(RedsState *reds, bool migrated):
reds(reds),
during_target_migrate(migrated)
{
pthread_mutex_init(&lock, NULL);
thread_id = pthread_self();
}
RedClient *red_client_new(RedsState *reds, int migrated)
{
return new RedClient(reds, migrated);
}
void RedClient::set_migration_seamless() // dest
{
RedChannelClient *rcc;
spice_assert(during_target_migrate);
pthread_mutex_lock(&lock);
seamless_migrate = TRUE;
/* update channel clients that got connected before the migration
* type was set. red_client_add_channel will handle newer channel clients */
FOREACH_CHANNEL_CLIENT(this, rcc) {
if (rcc->set_migration_seamless()) {
num_migrated_channels++;
}
}
pthread_mutex_unlock(&lock);
}
void RedClient::migrate()
{
RedChannelClient *rcc;
RedChannel *channel;
if (!pthread_equal(pthread_self(), thread_id)) {
spice_warning("client->thread_id (%p) != "
"pthread_self (%p)."
"If one of the threads is != io-thread && != vcpu-thread,"
" this might be a BUG",
(void*) thread_id, (void*) pthread_self());
}
FOREACH_CHANNEL_CLIENT(this, rcc) {
if (rcc->is_connected()) {
channel = rcc->get_channel();
channel->migrate_client(rcc);
}
}
}
void RedClient::destroy()
{
if (!pthread_equal(pthread_self(), thread_id)) {
spice_warning("client->thread_id (%p) != "
"pthread_self (%p)."
"If one of the threads is != io-thread && != vcpu-thread,"
" this might be a BUG",
(void*) thread_id,
(void*) pthread_self());
}
pthread_mutex_lock(&lock);
spice_debug("destroy this %p with #channels=%d", this, g_list_length(channels));
// This makes sure that we won't try to add new RedChannelClient instances
// to the RedClient::channels list while iterating it
disconnecting = TRUE;
while (channels) {
RedChannel *channel;
RedChannelClient *rcc = (RedChannelClient *) channels->data;
// Remove the RedChannelClient we are processing from the list
// Note that we own the object so it is safe to do some operations on it.
// This manual scan of the list is done to have a thread safe
// iteration of the list
channels = g_list_delete_link(channels, channels);
// prevent dead lock disconnecting rcc (which can happen
// in the same thread or synchronously on another one)
pthread_mutex_unlock(&lock);
// some channels may be in other threads, so disconnection
// is not synchronous.
channel = rcc->get_channel();
// some channels may be in other threads. However we currently
// assume disconnect is synchronous (we changed the dispatcher
// to wait for disconnection)
// TODO: should we go back to async. For this we need to use
// ref count for channel clients.
channel->disconnect_client(rcc);
spice_assert(rcc->pipe_is_empty());
spice_assert(rcc->no_item_being_sent());
rcc->unref();
pthread_mutex_lock(&lock);
}
pthread_mutex_unlock(&lock);
unref();
}
/* client->lock should be locked */
RedChannelClient *RedClient::get_channel(int type, int id)
{
RedChannelClient *rcc;
FOREACH_CHANNEL_CLIENT(this, rcc) {
RedChannel *channel;
channel = rcc->get_channel();
if (channel->type() == type && channel->id() == id) {
return rcc;
}
}
return NULL;
}
gboolean RedClient::add_channel(RedChannelClient *rcc, GError **error)
{
RedChannel *channel;
gboolean result = TRUE;
spice_assert(rcc);
channel = rcc->get_channel();
pthread_mutex_lock(&lock);
uint32_t type = channel->type();
uint32_t id = channel->id();
if (disconnecting) {
g_set_error(error,
SPICE_SERVER_ERROR,
SPICE_SERVER_ERROR_FAILED,
"Client %p got disconnected while connecting channel type %d id %d",
this, type, id);
result = FALSE;
goto cleanup;
}
if (get_channel(type, id)) {
g_set_error(error,
SPICE_SERVER_ERROR,
SPICE_SERVER_ERROR_FAILED,
"Client %p: duplicate channel type %d id %d",
this, type, id);
result = FALSE;
goto cleanup;
}
// first must be the main one
if (!mcc) {
// FIXME use dynamic_cast to check type
// spice_assert(MAIN_CHANNEL_CLIENT(rcc) != NULL);
rcc->ref();
mcc = (MainChannelClient *) rcc;
}
channels = g_list_prepend(channels, rcc);
if (during_target_migrate && seamless_migrate) {
if (rcc->set_migration_seamless()) {
num_migrated_channels++;
}
}
cleanup:
pthread_mutex_unlock(&lock);
return result;
}
MainChannelClient *RedClient::get_main()
{
return mcc;
}
void RedClient::semi_seamless_migrate_complete()
{
RedChannelClient *rcc;
pthread_mutex_lock(&lock);
if (!during_target_migrate || seamless_migrate) {
spice_error("unexpected");
pthread_mutex_unlock(&lock);
return;
}
during_target_migrate = FALSE;
FOREACH_CHANNEL_CLIENT(this, rcc) {
rcc->semi_seamless_migration_complete();
}
pthread_mutex_unlock(&lock);
reds_on_client_semi_seamless_migrate_complete(reds, this);
}
/* should be called only from the main thread */
int RedClient::during_migrate_at_target()
{
int ret;
pthread_mutex_lock(&lock);
ret = during_target_migrate;
pthread_mutex_unlock(&lock);
return ret;
}
void RedClient::remove_channel(RedChannelClient *rcc)
{
RedClient *client = rcc->get_client();
pthread_mutex_lock(&client->lock);
GList *link = g_list_find(client->channels, rcc);
if (link) {
client->channels = g_list_delete_link(client->channels, link);
}
pthread_mutex_unlock(&client->lock);
if (link) {
rcc->unref();
}
}
/* returns TRUE If all channels are finished migrating, FALSE otherwise */
gboolean RedClient::seamless_migration_done_for_channel()
{
gboolean ret = FALSE;
pthread_mutex_lock(&lock);
num_migrated_channels--;
/* we assume we always have at least one channel who has migration data transfer,
* otherwise, this flag will never be set back to FALSE*/
if (!num_migrated_channels) {
during_target_migrate = FALSE;
seamless_migrate = FALSE;
/* migration completion might have been triggered from a different thread
* than the main thread */
reds_get_main_dispatcher(reds)->seamless_migrate_dst_complete(this);
ret = TRUE;
}
pthread_mutex_unlock(&lock);
return ret;
}
gboolean RedClient::is_disconnecting()
{
gboolean ret;
pthread_mutex_lock(&lock);
ret = disconnecting;
pthread_mutex_unlock(&lock);
return ret;
}
void RedClient::set_disconnecting()
{
pthread_mutex_lock(&lock);
disconnecting = TRUE;
pthread_mutex_unlock(&lock);
}
RedsState *RedClient::get_server()
{
return reds;
}