[Spice-devel] [PATCH spice-gtk V4 1/3] file-xfer: handling various transfer messages in main channel
Dunrong Huang
riegamaths at gmail.com
Fri Jan 4 00:34:31 PST 2013
Hi, thanks for your review.
On Tue, Jan 1, 2013 at 1:51 AM, Marc-André Lureau
<marcandre.lureau at gmail.com> wrote:
>
> Hi!
>
> On Fri, Dec 28, 2012 at 2:33 PM, Dunrong Huang <riegamaths at gmail.com> wrote:
>>
>> Agent channel is a flow-control channel. That means before
>> we send agent data to server, we must obtain tokens distributed
>> from spice server, if we do not do that, spice server will get error,
>> or at least, the data will be discarded.
>>
>> Other type of agent data will be cached to agent_msg_queue if there
>> are no more tokens. But for file-xfer data, if we cache too much of
>> those data, our memory will be exhausted pretty quickly if file is
>> too big.
>>
>> We also should make other agent data(clipboard, mouse, ...) get
>> through when file-xfer data are sending.
>>
>> So, for the reason of above, we can not fill file-xfer data to agent queue
>> too quickly, we must consider the tokens, and other messages.
>>
>> Marc-André suggested me to call spice_channel_flush_async() and wait the
>> queued data to be sent, but the API does not consider the available
>> tokens, so I use a new algorithm/API(file_xfer_flush_async) based on
>> spice_channel_flush_async() to send file-xfer data.
>>
>
> Right
>
>> +
>> +static void data_flushed_cb(GObject *source_object,
>> + GAsyncResult *res,
>> + gpointer user_data)
>> +{
>> + SpiceFileXferTask *task = user_data;
>> + SpiceMainChannel *channel = (SpiceMainChannel *)source_object;
>> + GError *error = NULL;
>> +
>> + file_xfer_flush_finish(channel, res, &error);
>
>
> Even if the function currently doesn't report error, you should treat error
> case at least with a g_warning().
>
Yes
>> +
>> +/* main context */
>> +static void file_read_cb(GObject *source_object,
>> + GAsyncResult *res,
>> + gpointer user_data)
>> +{
>> + SpiceFileXferTask *task = user_data;
>> + SpiceMainChannel *channel = task->channel;
>> + gssize count;
>> + GError *error = NULL;
>> +
>> + count = g_input_stream_read_finish(G_INPUT_STREAM(task->file_stream),
>> + res, &error);
>> + if (count > 0) {
>> + task->read_bytes += count;
>> + file_xfer_queue(task, count);
>> + file_xfer_flush_async(channel, task->cancellable,
>> + data_flushed_cb, task);
>> + } else {
>> + g_input_stream_close_async(G_INPUT_STREAM(task->file_stream),
>> + G_PRIORITY_DEFAULT,
>> + task->cancellable,
>> + file_close_cb,
>> + task);
>
>
> You should report error to caller (probably with
> g_simple_async_report_gerror_in_idle)
>
> There will be some issues with the fact that there are multiple outstanding
> async in the background. The common pattern is to _always_ complete one
> async() call with one result (succesful or error). There shouldn't be
> "lost" async, or you may "block" some client execution path.
>
Will be fixed in next version.
>> +
>> +/* coroutine context */
>> +static void file_xfer_handle_status(SpiceMainChannel *channel,
>> + VDAgentFileXferStatusMessage *msg)
>> +{
>> + SPICE_DEBUG("task %d received response %d", msg->id, msg->result);
>
>
> You could lookup the task only once here.
>
>> +
>> + if (msg->result == VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA) {
>> + file_xfer_send_data_msg(channel, msg->id);
>
>
> and can call g_input_stream_read_async() directly here, or perhaps move the
> read_async() in a seperate function task_continue_read() which will be
> called also from data_flushed_cb().
>
Will be fixed in next version.
>>
>> + } else {
>
>
> Please check precisely the other msg->result values, and do a
> g_warn_if_reached() for unknown values.
>
Ok.
>> + /* Error, remove this task */
>> + SpiceMainChannelPrivate *c = channel->priv;
>> + GList *l;
>> + SpiceFileXferTask *task;
>> +
>> + l = g_list_find_custom(c->file_xfer_task_list, &msg->id,
>> + file_xfer_task_find);
>> + g_return_if_fail(l != NULL);
>> +
>> + task = l->data;
>> + SPICE_DEBUG("user removed task %d, result: %d", msg->id,
>> + msg->result);
>
>
> You should report error to caller (probably with
> g_simple_async_report_gerror_in_idle)
>
Ok.
>
>
>> + c->file_xfer_task_list = g_list_remove(c->file_xfer_task_list,
>> + task);
>> + g_object_unref(task->file);
>> + g_object_unref(task->file_stream);
>> + g_free(task);
>
>
> Those last 4 lines could probably be in a seperate function
> file_xfer_task_free ()
>
Agree.
>> + }
>> +}
>> +
>> /* coroutine context */
>> static void main_agent_handle_msg(SpiceChannel *channel,
>> VDAgentMessage *msg, gpointer payload)
>> @@ -1487,6 +1765,9 @@ static void main_agent_handle_msg(SpiceChannel
>> *channel,
>> reply->error == VD_AGENT_SUCCESS ? "success" :
>> "error");
>> break;
>> }
>> + case VD_AGENT_FILE_XFER_STATUS:
>> + file_xfer_handle_status(SPICE_MAIN_CHANNEL(channel), payload);
>> + break;
>> default:
>> g_warning("unhandled agent message type: %u (%s), size %u",
>> msg->type, NAME(agent_msg_types, msg->type),
>> msg->size);
>> @@ -1563,6 +1844,7 @@ static void main_handle_agent_token(SpiceChannel
>> *channel, SpiceMsgIn *in)
>> SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
>>
>> c->agent_tokens += tokens->num_tokens;
>> +
>> agent_send_msg_queue(SPICE_MAIN_CHANNEL(channel));
>> }
>>
>> @@ -2246,3 +2528,197 @@ void
>> spice_main_set_display_enabled(SpiceMainChannel *channel, int id, gboolean
>> c->display[id].enabled = enabled;
>> }
>> }
>> +
>> +static void
>> +file_info_async_cb(GObject *obj, GAsyncResult *res, gpointer data)
>> +{
>> + GFileInfo *info;
>> + GFile *file = G_FILE(obj);
>> + GError *error = NULL;
>> + GKeyFile *keyfile = NULL;
>> + gchar *basename = NULL;
>> + VDAgentFileXferStartMessage *msg;
>> + gsize msg_size, data_len;
>> + gchar *string;
>> + SpiceFileXferTask *task = (SpiceFileXferTask *)data;
>> + SpiceMainChannelPrivate *c = task->channel->priv;
>> +
>> + info = g_file_query_info_finish(file, res, &error);
>> + if (error) {
>> + SPICE_DEBUG("couldn't get size of file %s: %s",
>> + g_file_get_path(file),
>> + error->message);
>> + goto failed;
>> + }
>> + task->file_size = g_file_info_get_attribute_uint64(info,
>> + G_FILE_ATTRIBUTE_STANDARD_SIZE);
>> +
>> + keyfile = g_key_file_new();
>> + if (keyfile == NULL) {
>> + SPICE_DEBUG("failed to create key file: %s", error->message);
>> + goto failed;
>> + }
>> +
>> + /* File name */
>> + basename = g_file_get_basename(file);
>> + if (basename == NULL) {
>> + SPICE_DEBUG("failed to get file basename: %s", error->message);
>> + goto failed;
>> + }
>> + g_key_file_set_string(keyfile, "vdagent-file-xfer", "name",
>> basename);
>> + g_free(basename);
>> +
>> + /* File size */
>> + g_key_file_set_uint64(keyfile, "vdagent-file-xfer", "size",
>> + task->file_size);
>> +
>> + /* Save keyfile content to memory. TODO: more file attributions
>> + need to be sent to guest */
>> + string = g_key_file_to_data(keyfile, &data_len, &error);
>> + g_key_file_free(keyfile);
>> + if (error) {
>> + goto failed;
>> + }
>> +
>> + /* Create file-xfer start message */
>> + msg_size = sizeof(VDAgentFileXferStartMessage) + data_len + 1;
>> + msg = g_malloc0(msg_size);
>
>
> This allocation and copy seems unnecessary, can you use "string" directly?
>
Ok, since msg->data is a zero-length array, we can do it by using
agent_msg_queue_many(). will be fixed next version.
>> + msg->id = task->id;
>> + memcpy(msg->data, string, data_len + 1);
>> + g_free(string);
>> +
>> + CHANNEL_DEBUG(task->channel, "Insert a xfer task:%d to task list",
>> + task->id);
>> + c->file_xfer_task_list = g_list_append(c->file_xfer_task_list, task);
>> +
>> + agent_msg_queue(task->channel, VD_AGENT_FILE_XFER_START, msg_size,
>> msg);
>> + g_free(msg);
>> + spice_channel_wakeup(SPICE_CHANNEL(task->channel), FALSE);
>> + return ;
>> +
>> +failed:
>> + g_clear_error(&error);
>> + g_object_unref(task->file);
>> + g_object_unref(task->file_stream);
>> + g_free(task);
>> +}
>> +
>> +static void
>> +read_async_cb(GObject *obj, GAsyncResult *res, gpointer data)
>> +{
>> + GFile *file = G_FILE(obj);
>> + SpiceFileXferTask *task = (SpiceFileXferTask *)data;
>> + GError *error = NULL;
>> +
>> + task->file_stream = g_file_read_finish(file, res, &error);
>> +
>> + if (task->file_stream) {
>> + g_file_query_info_async(task->file,
>> + G_FILE_ATTRIBUTE_STANDARD_SIZE,
>> + G_FILE_QUERY_INFO_NONE,
>> + G_PRIORITY_DEFAULT,
>> + task->cancellable,
>> + file_info_async_cb,
>> + task);
>> + } else {
>> + SPICE_DEBUG("create file stream for %s error: %s",
>> + g_file_get_path(file), error->message);
>> + g_clear_error(&error);
>> + g_object_unref(task->file);
>> + g_free(task);
>
>
> You should report error to caller (probably with
> g_simple_async_report_gerror_in_idle)
>
Ok.
>> +void spice_main_file_copy_async(SpiceMainChannel *channel,
>> + GFile **sources,
>> + GFileCopyFlags flags,
>> + GCancellable *cancellable,
>> + GFileProgressCallback progress_callback,
>> + gpointer progress_callback_data,
>> + GAsyncReadyCallback callback,
>> + gpointer user_data)
>> +{
>> + int i = 0;
>> + static uint32_t xfer_group_id;
>> +
>> + g_return_if_fail(channel != NULL);
>> + g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
>> + g_return_if_fail(sources != NULL);
>> +
>> + xfer_group_id++;
>> + xfer_group_id = (xfer_group_id > UINT32_MAX) ? 0 : xfer_group_id;
>> + while (sources[i]) {
>> + /* All tasks created from below function have same group id */
>
>
> I am worried by the server side handling of sharing the same group id for
> several requests. But I am okay with this communication pattern that can be
> later improved if needed.
>
Hi, Marc-André
spice_main_file_copy_async() was designed to copy multi-files at a
time, which I think is a little unreasonable:
1) If something error happens when opening or reading a file. should
we report error immediately(using callback), or wait for all tasks to
complete?
2) We have to set group_id to identify tasks created at the same
time(e.g. for report_progress, report_finish, ...)
3) For reporting progress, we have to calculate all tasks' remain
bytes(see report_progress()), this is a bit weird. It should be done
by caller of this API(just as nautilus does).
4) After a task is finished, we have to check whether other tasks with
same group_id have been finished(see report_finish()). I think it's a
bit weird.
If spice_main_file_copy_async() only copys one file at a time, our
code will be more simple and clean. The caller of this API can
implement
coping multi-files, just as nautilus does.
So, our client API shoule be like this:
spice_main_file_copy_async(SpiceMainChannel *channel, GFile *,
GFileCopyFlags, GCancellable *, GFileProgressCallback , gpointer ,
GAsyncReadyCallback ,gpointer )
And we also need spice_main_file_copy_finish() to finish operation.
Can you agree with me?
--
Best Regards,
Dunrong Huang
More information about the Spice-devel
mailing list