1 /*
2 Copyright (C) 2009-2016 Red Hat, Inc.
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public
15 License along with this library; if not, see <http://www.gnu.org/licenses/>.
16
17 */
18 #include <config.h>
19
20 #include "red-channel.h"
21 #include "red-client.h"
22 #include "reds.h"
23
24 #define FOREACH_CHANNEL_CLIENT(_client, _data) \
25 for (const auto &_data: _client->channels)
26
~RedClient()27 RedClient::~RedClient()
28 {
29 spice_debug("release client=%p", this);
30 pthread_mutex_destroy(&lock);
31 }
32
RedClient(RedsState * init_reds,bool migrated)33 RedClient::RedClient(RedsState *init_reds, bool migrated):
34 reds(init_reds),
35 during_target_migrate(migrated)
36 {
37 pthread_mutex_init(&lock, nullptr);
38 thread_id = pthread_self();
39 }
40
red_client_new(RedsState * reds,int migrated)41 RedClient *red_client_new(RedsState *reds, int migrated)
42 {
43 return new RedClient(reds, migrated);
44 }
45
set_migration_seamless()46 void RedClient::set_migration_seamless() // dest
47 {
48 spice_assert(during_target_migrate);
49 pthread_mutex_lock(&lock);
50 seamless_migrate = TRUE;
51 /* update channel clients that got connected before the migration
52 * type was set. red_client_add_channel will handle newer channel clients */
53 FOREACH_CHANNEL_CLIENT(this, rcc) {
54 if (rcc->set_migration_seamless()) {
55 num_migrated_channels++;
56 }
57 }
58 pthread_mutex_unlock(&lock);
59 }
60
migrate()61 void RedClient::migrate()
62 {
63 if (!pthread_equal(pthread_self(), thread_id)) {
64 spice_warning("client->thread_id (%p) != "
65 "pthread_self (%p)."
66 "If one of the threads is != io-thread && != vcpu-thread,"
67 " this might be a BUG",
68 (void*) thread_id, (void*) pthread_self());
69 }
70 FOREACH_CHANNEL_CLIENT(this, rcc) {
71 if (rcc->is_connected()) {
72 auto channel = rcc->get_channel();
73 channel->migrate_client(rcc.get());
74 }
75 }
76 }
77
destroy()78 void RedClient::destroy()
79 {
80 if (!pthread_equal(pthread_self(), thread_id)) {
81 spice_warning("client->thread_id (%p) != "
82 "pthread_self (%p)."
83 "If one of the threads is != io-thread && != vcpu-thread,"
84 " this might be a BUG",
85 (void*) thread_id,
86 (void*) pthread_self());
87 }
88
89 pthread_mutex_lock(&lock);
90 spice_debug("destroy this %p with #channels=%zd", this, channels.size());
91 // This makes sure that we won't try to add new RedChannelClient instances
92 // to the RedClient::channels list while iterating it
93 disconnecting = TRUE;
94 while (!channels.empty()) {
95 auto rcc = *channels.begin();
96
97 // Remove the RedChannelClient we are processing from the list
98 // Note that we own the object so it is safe to do some operations on it.
99 // This manual scan of the list is done to have a thread safe
100 // iteration of the list
101 channels.pop_front();
102
103 // prevent dead lock disconnecting rcc (which can happen
104 // in the same thread or synchronously on another one)
105 pthread_mutex_unlock(&lock);
106
107 // some channels may be in other threads, so disconnection
108 // is not synchronous.
109 auto channel = rcc->get_channel();
110
111 // some channels may be in other threads. However we currently
112 // assume disconnect is synchronous (we changed the dispatcher
113 // to wait for disconnection)
114 // TODO: should we go back to async. For this we need to use
115 // ref count for channel clients.
116 channel->disconnect_client(rcc.get());
117
118 spice_assert(rcc->pipe_is_empty());
119 spice_assert(rcc->no_item_being_sent());
120
121 pthread_mutex_lock(&lock);
122 }
123 pthread_mutex_unlock(&lock);
124 unref();
125 }
126
127
128 /* client->lock should be locked */
get_channel(int type,int id)129 RedChannelClient *RedClient::get_channel(int type, int id)
130 {
131 FOREACH_CHANNEL_CLIENT(this, rcc) {
132 RedChannel *channel;
133
134 channel = rcc->get_channel();
135 if (channel->type() == type && channel->id() == id) {
136 return rcc.get();
137 }
138 }
139 return nullptr;
140 }
141
add_channel(RedChannelClient * rcc,char ** error)142 gboolean RedClient::add_channel(RedChannelClient *rcc, char **error)
143 {
144 RedChannel *channel;
145 gboolean result = TRUE;
146
147 spice_assert(rcc);
148 channel = rcc->get_channel();
149
150 pthread_mutex_lock(&lock);
151
152 uint32_t type = channel->type();
153 uint32_t id = channel->id();
154 if (disconnecting) {
155 *error =
156 g_strdup_printf("Client %p got disconnected while connecting channel type %d id %d",
157 this, type, id);
158 result = FALSE;
159 goto cleanup;
160 }
161
162 if (get_channel(type, id)) {
163 *error =
164 g_strdup_printf("Client %p: duplicate channel type %d id %d",
165 this, type, id);
166 result = FALSE;
167 goto cleanup;
168 }
169
170 // first must be the main one
171 if (!mcc) {
172 // FIXME use dynamic_cast to check type
173 // spice_assert(MAIN_CHANNEL_CLIENT(rcc) != NULL);
174 mcc.reset((MainChannelClient *) rcc);
175 }
176 channels.push_front(red::shared_ptr<RedChannelClient>(rcc));
177 if (during_target_migrate && seamless_migrate) {
178 if (rcc->set_migration_seamless()) {
179 num_migrated_channels++;
180 }
181 }
182
183 cleanup:
184 pthread_mutex_unlock(&lock);
185 return result;
186 }
187
get_main()188 MainChannelClient *RedClient::get_main()
189 {
190 return mcc.get();
191 }
192
semi_seamless_migrate_complete()193 void RedClient::semi_seamless_migrate_complete()
194 {
195 pthread_mutex_lock(&lock);
196 if (!during_target_migrate || seamless_migrate) {
197 spice_error("unexpected");
198 pthread_mutex_unlock(&lock);
199 return;
200 }
201 during_target_migrate = FALSE;
202 FOREACH_CHANNEL_CLIENT(this, rcc) {
203 rcc->semi_seamless_migration_complete();
204 }
205 pthread_mutex_unlock(&lock);
206 reds_on_client_semi_seamless_migrate_complete(reds, this);
207 }
208
209 /* should be called only from the main thread */
during_migrate_at_target()210 int RedClient::during_migrate_at_target()
211 {
212 int ret;
213 pthread_mutex_lock(&lock);
214 ret = during_target_migrate;
215 pthread_mutex_unlock(&lock);
216 return ret;
217 }
218
remove_channel(RedChannelClient * rcc)219 void RedClient::remove_channel(RedChannelClient *rcc)
220 {
221 RedClient *client = rcc->get_client();
222 red::shared_ptr<RedChannelClient> holding_rcc(rcc);
223 pthread_mutex_lock(&client->lock);
224 client->channels.remove(holding_rcc);
225 pthread_mutex_unlock(&client->lock);
226 }
227
228 /* returns TRUE If all channels are finished migrating, FALSE otherwise */
seamless_migration_done_for_channel()229 gboolean RedClient::seamless_migration_done_for_channel()
230 {
231 gboolean ret = FALSE;
232
233 pthread_mutex_lock(&lock);
234 num_migrated_channels--;
235 /* we assume we always have at least one channel who has migration data transfer,
236 * otherwise, this flag will never be set back to FALSE*/
237 if (!num_migrated_channels) {
238 during_target_migrate = FALSE;
239 seamless_migrate = FALSE;
240 /* migration completion might have been triggered from a different thread
241 * than the main thread */
242 reds_get_main_dispatcher(reds)->seamless_migrate_dst_complete(this);
243 ret = TRUE;
244 }
245 pthread_mutex_unlock(&lock);
246
247 return ret;
248 }
249
is_disconnecting()250 gboolean RedClient::is_disconnecting()
251 {
252 gboolean ret;
253 pthread_mutex_lock(&lock);
254 ret = disconnecting;
255 pthread_mutex_unlock(&lock);
256 return ret;
257 }
258
set_disconnecting()259 void RedClient::set_disconnecting()
260 {
261 pthread_mutex_lock(&lock);
262 disconnecting = TRUE;
263 pthread_mutex_unlock(&lock);
264 }
265
get_server()266 RedsState *RedClient::get_server()
267 {
268 return reds;
269 }
270