Philip Withnall
philip.withnall@collabora.co.uk 2015
개별 `GSource` 구현체 작성 지침서
조성호
shcho@gnome.org 2017-2019.

# 개별 GSource

#

        요약

        이 글은 개별 GSource를 만드는 지침 내용을 담고 있습니다. 참고 문서를 찾아보려면 GLib API 참고서를 살펴보십시오.

# `GSource`가 뭐죠?

`GSource`는 이벤트를 받았을때 실행할 콜백 함수와 관계있는 기대 이벤트입니다. 예를 들어 이벤트는 시간 초과 상황 또는 소켓을 통해 데이터를 받았을 때가 될 수 있습니다.

GLib에는 다양한 `GSource`가 있지만, 메인 루프에 통합할 개별 이벤트를 허용하여 프로그램 자체에서의 `GSource` 정의를 허용합니다.

`GSource` 구조와 가상 함수에 대한 자세한 문서는 GLib API 참고서에 있습니다.

# 메시지 큐 소스

실행 예제와 같이, 소스 내부 큐에 메시지를 쌓아둘 때마다 (잠재적으로 다른 스레드에서) 콜백을 실행할 때 메시지 큐 소스를 사용합니다.

이 소스 형식은 메인 컨텍스트간 상당한 양의 메시지를 효율적으로 주고 받을때 쓸만합니다. 대신, `g_source_attach()` 함수를 활용하여 각 메시지를 개별 대기 `GSource` 로 보내는 방법이 있습니다. 수많은 메시지에 대해 `GSource`에 대해 메모리를 할당하고 해제함을 의미합니다.

## 구조체

우선 source 구조를 선언해야합니다. `GSource`를 상위 구조로 넣어야하며 그 다음 source 자체 필드를 넣습니다. 큐와 각 메시지를 처리하고 나서 메모리 할당을 해제할 함수가 이에 해당합니다.

typedef struct { GSource parent; GAsyncQueue *queue; /* owned */ GDestroyNotify destroy_message; } MessageQueueSource;

## 준비 함수

그 다음, source의 준비 함수를 정의해야합니다. source를 처리할 준비가 됐는지 여부를 결정합니다. source가 인메모리 큐를 사용하기 때문에 큐 길이를 검사하여 결정할 수 있습니다. 큐에 구성요소를 넣어두었다면 source에서 해당 구성 요소를 처리할 수 있습니다.

return (g_async_queue_length (message_queue_source->queue) > 0);

## 점검 함수

이 소스에 파일 서술자가 없기에, 준비하고 검사하는 함수는 근본적으로 동일한 동작을 취합니다. 따라서 검사 함수가 필요하지 않습니다. `GSourceFuncs`에서 필드 값을 `NULL`로 설정하면 소스 형식에 대한 검사 함수 동작을 건너뜁니다.

## 실행 함수

이 source에 대해, 실행 함수는 복잡성이 존재하는 곳입니다. 큐에서 메시지를 빼낸 후 `GSource`의 콜백 함수에 메시지를 전달해야합니다. 큐에 메시지가 없을 수도 있습니다. 준비 함수에서 true를 반환한다 하더라도, 동일한 큐를 래핑하는 다른 source를 평균 시간내에 처리하며 큐에서 마지막 메시지를 취하기도 합니다. 게다가 `GSource`에 (허용한) 콜백을 설정하지 않으면, 메시지를 해체하고 버려야합니다.

메시지와 콜백을 설정하면 콜백에서 메시지를 처리하고, 실행 함수의 반환 값으로 반환 값을 전달합니다. `GSource`가 해체되면 `FALSE`, `GSourceFunc`에 대해 계속 남아있으면 `TRUE`입니다. 이런 식의 의미는 모든 실행 함수 구현에 동일하게 적용합니다.

/* Pop a message off the queue. */ message = g_async_queue_try_pop (message_queue_source->queue);

/* If there was no message, bail. */ if (message == NULL) { /* Keep the source around to handle the next message. */ return TRUE; }

/* @func may be %NULL if no callback was specified. * If so, drop the message. */ if (func == NULL) { if (message_queue_source->destroy_message != NULL) { message_queue_source->destroy_message (message); }

/* Keep the source around to consume the next message. */ return TRUE; }

return func (message, user_data);

## 콜백 함수

`GSource` 콜백은 `GSourceFunc` 형식일 필요는 없습니다. 이 형식에 대해 충분히 문서로 남겨둔 만큼 source의 실행 함수에서 불리는 어떤 함수 형식이든 될 수 있습니다.

보통, 소스 인스턴스에서 콜백 함수를 설정할 때 `g_source_set_callback()` 함수를 사용합니다. `GDestroyNotify`를 함께 사용하면, 소스가 계속 살아있는 동안 객체의 강 참조를 유지할 수 있습니다:

g_source_set_callback (source, callback_func, g_object_ref (object_to_strong_ref), (GDestroyNotify) g_object_unref);

그러나, `GSource`는 `g_source_set_callback_indirect()`로 노출하여 콜백을 전달하는 간접 계층입니다. GObject는 객체 사용을 마감했을 때 소스에서 자동으로 해제하는 소스에 `GClosure`를 콜백으로 설정할 수 있게 합니다 — 위의 강 참조에 대비되는 약참조는 다음과 같습니다:

g_source_set_closure (source, g_cclosure_new_object (callback_func, object_to_weak_ref));

소스에서는 콜백이 필요하지만 콜백에서 동작이 필요하지 않은 경우 사용할 수 있는 일반 클로저 기반 'dummy' 콜백을 허용합니다:

g_source_set_dummy_callback (source);

## 생성자

마지막으로 `GSource`의 `GSourceFuncs` 정의를 생성자 함수에 작성할 수 있습니다. 보통 하위 형식 구조로서가 아닌 `GSource`로서 새 source 형식을 노출하는 전형적인 사례입니다. 이렇게 하여 생성자에서 `GSource*`를 반환합니다.

여기 예제 생성자에서는 편하게 취소하는 방식을 지원하는 하위 source 활용법을 예를 들어 보여줍니다. `GCancellable`를 취소처리하면, 프로그램의 콜백 함수를 실행하여 취소 여부를확인할 수 있습니다(프로그램 코드에서는 `g_source_set_callback()` 함수의 콜백 사용자 데이터 세트로, 콜백 함수에 `GCancellable` 에 접근할 수 있는 포인터를 만들어아합니다.)

GSource * message_queue_source_new (GAsyncQueue *queue, GDestroyNotify destroy_message, GCancellable *cancellable) { GSource *source; /* alias of @message_queue_source */ MessageQueueSource *message_queue_source; /* alias of @source */

g_return_val_if_fail (queue != NULL, NULL); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);

source = g_source_new (&message_queue_source_funcs, sizeof (MessageQueueSource)); message_queue_source = (MessageQueueSource *) source;

/* The caller can overwrite this name with something more useful later. */ g_source_set_name (source, "MessageQueueSource");

message_queue_source->queue = g_async_queue_ref (queue); message_queue_source->destroy_message = destroy_message;

/* Add a cancellable source. */ if (cancellable != NULL) { GSource *cancellable_source;

cancellable_source = g_cancellable_source_new (cancellable); g_source_set_dummy_callback (cancellable_source); g_source_add_child_source (source, cancellable_source); g_source_unref (cancellable_source); }

return source; }

# 완전한 예제

## 완전한 예제 코드

/** * MessageQueueSource: * * This is a #GSource which wraps a #GAsyncQueue and is dispatched whenever a * message can be pulled off the queue. Messages can be enqueued from any * thread. * * The callbacks dispatched by a #MessageQueueSource have type * #MessageQueueSourceFunc. * * #MessageQueueSource supports adding a #GCancellable child source which will * additionally dispatch if a provided #GCancellable is cancelled. */ typedef struct { GSource parent; GAsyncQueue *queue; /* owned */ GDestroyNotify destroy_message; } MessageQueueSource;

/** * MessageQueueSourceFunc: * @message: (transfer full) (nullable): message pulled off the queue * @user_data: user data provided to g_source_set_callback() * * Callback function type for #MessageQueueSource. */ typedef gboolean (*MessageQueueSourceFunc) (gpointer message, gpointer user_data);

static gboolean message_queue_source_prepare (GSource *source, gint *timeout_) { MessageQueueSource *message_queue_source = (MessageQueueSource *) source;

return (g_async_queue_length (message_queue_source->queue) > 0); }

static gboolean message_queue_source_dispatch (GSource *source, GSourceFunc callback, gpointer user_data) { MessageQueueSource *message_queue_source = (MessageQueueSource *) source; gpointer message; MessageQueueSourceFunc func = (MessageQueueSourceFunc) callback;

/* Pop a message off the queue. */ message = g_async_queue_try_pop (message_queue_source->queue);

/* If there was no message, bail. */ if (message == NULL) { /* Keep the source around to handle the next message. */ return TRUE; }

/* @func may be %NULL if no callback was specified. * If so, drop the message. */ if (func == NULL) { if (message_queue_source->destroy_message != NULL) { message_queue_source->destroy_message (message); }

/* Keep the source around to consume the next message. */ return TRUE; }

return func (message, user_data); }

static void message_queue_source_finalize (GSource *source) { MessageQueueSource *message_queue_source = (MessageQueueSource *) source;

g_async_queue_unref (message_queue_source->queue); }

static gboolean message_queue_source_closure_callback (gpointer message, gpointer user_data) { GClosure *closure = user_data; GValue param_value = G_VALUE_INIT; GValue result_value = G_VALUE_INIT; gboolean retval;

/* The invoked function is responsible for freeing @message. */ g_value_init (&result_value, G_TYPE_BOOLEAN); g_value_init (&param_value, G_TYPE_POINTER); g_value_set_pointer (&param_value, message);

g_closure_invoke (closure, &result_value, 1, &param_value, NULL); retval = g_value_get_boolean (&result_value);

g_value_unset (&param_value); g_value_unset (&result_value);

return retval; }

static GSourceFuncs message_queue_source_funcs = { message_queue_source_prepare, NULL, /* check */ message_queue_source_dispatch, message_queue_source_finalize, (GSourceFunc) message_queue_source_closure_callback, NULL, };

/** * message_queue_source_new: * @queue: the queue to check * @destroy_message: (nullable): function to free a message, or %NULL * @cancellable: (nullable): a #GCancellable, or %NULL * * Create a new #MessageQueueSource, a type of #GSource which dispatches for * each message queued to it. * * If a callback function of type #MessageQueueSourceFunc is connected to the * returned #GSource using g_source_set_callback(), it will be invoked for each * message, with the message passed as its first argument. It is responsible for * freeing the message. If no callback is set, messages are automatically freed * as they are queued. * * Returns: (transfer full): a new #MessageQueueSource */ GSource * message_queue_source_new (GAsyncQueue *queue, GDestroyNotify destroy_message, GCancellable *cancellable) { GSource *source; /* alias of @message_queue_source */ MessageQueueSource *message_queue_source; /* alias of @source */

g_return_val_if_fail (queue != NULL, NULL); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);

source = g_source_new (&message_queue_source_funcs, sizeof (MessageQueueSource)); message_queue_source = (MessageQueueSource *) source;

/* The caller can overwrite this name with something more useful later. */ g_source_set_name (source, "MessageQueueSource");

message_queue_source->queue = g_async_queue_ref (queue); message_queue_source->destroy_message = destroy_message;

/* Add a cancellable source. */ if (cancellable != NULL) { GSource *cancellable_source;

cancellable_source = g_cancellable_source_new (cancellable); g_source_set_dummy_callback (cancellable_source); g_source_add_child_source (source, cancellable_source); g_source_unref (cancellable_source); }

return source; }

# 더 많은 예제

위에 보여드린 예제보다 소스가 더 복잡할 수 있습니다. libnice에서 자체 정의한 `GSource`는 동적으로 바뀌는 소켓 세트를 폴링 처리해야 합니다. 구현체는 component.c의 `ComponentSource`를 들 수 있으며, 준비 함수보다 더욱 복잡한 활용의 예를 보여줍니다.

Another example is a custom source to interface GnuTLS with GLib in its `GTlsConnection` implementation. `GTlsConnectionGnutlsSource` synchronizes the main thread and a TLS worker thread which performs the blocking TLS operations.