-
Notifications
You must be signed in to change notification settings - Fork 34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(listener): close with timeout #198
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -216,23 +216,19 @@ ServerListenerCallback(__unused_parm__ HQUIC Listener, | |
|
||
case QUIC_LISTENER_EVENT_STOP_COMPLETE: | ||
env = l_ctx->env; | ||
|
||
// Close listener in NIF CTX leads to NULL Listener HQUIC | ||
assert(l_ctx->Listener == NULL); | ||
|
||
// Dummy call to prevent leakage if handle is not NULL | ||
// @TODO they should be removed when we support ListenerStop call | ||
MsQuic->ListenerClose(l_ctx->Listener); | ||
l_ctx->Listener = NULL; | ||
|
||
enif_send(NULL, | ||
&(l_ctx->listenerPid), | ||
NULL, | ||
enif_make_tuple3(env, | ||
ATOM_QUIC, | ||
ATOM_LISTENER_STOPPED, | ||
enif_make_resource(env, l_ctx))); | ||
is_destroy = TRUE; | ||
if (!l_ctx->Listener) | ||
{ | ||
// @NOTE This callback is part of the listener *close* process | ||
// Listener is already closing, we can destroy the l_ctx now. | ||
is_destroy = TRUE; | ||
} | ||
enif_clear_env(env); | ||
break; | ||
default: | ||
|
@@ -284,7 +280,6 @@ listen2(ErlNifEnv *env, __unused_parm__ int argc, const ERL_NIF_TERM argv[]) | |
return ERROR_TUPLE_2(ATOM_BADARG); | ||
} | ||
|
||
|
||
// Build CredConfig | ||
QUIC_CREDENTIAL_CONFIG CredConfig; | ||
CxPlatZeroMemory(&CredConfig, sizeof(QUIC_CREDENTIAL_CONFIG)); | ||
|
@@ -452,6 +447,8 @@ listen2(ErlNifEnv *env, __unused_parm__ int argc, const ERL_NIF_TERM argv[]) | |
l_ctx->Listener, alpn_buffers, alpn_buffer_length, &Address))) | ||
{ | ||
TP_NIF_3(start_fail, (uintptr_t)(l_ctx->Listener), Status); | ||
MsQuic->ListenerClose(l_ctx->Listener); | ||
l_ctx->Listener = NULL; | ||
destroy_l_ctx(l_ctx); | ||
return ERROR_TUPLE_3(ATOM_LISTENER_START_ERROR, ATOM_STATUS(Status)); | ||
} | ||
|
@@ -465,21 +462,140 @@ close_listener1(ErlNifEnv *env, | |
const ERL_NIF_TERM argv[]) | ||
{ | ||
QuicerListenerCTX *l_ctx; | ||
BOOLEAN is_destroy = FALSE; | ||
ERL_NIF_TERM ret = ATOM_OK; | ||
if (!enif_get_resource(env, argv[0], ctx_listener_t, (void **)&l_ctx)) | ||
{ | ||
return ERROR_TUPLE_2(ATOM_BADARG); | ||
} | ||
|
||
enif_mutex_lock(l_ctx->lock); | ||
if (l_ctx->is_closed) | ||
{ | ||
enif_mutex_unlock(l_ctx->lock); | ||
return ERROR_TUPLE_2(ATOM_CLOSED); | ||
} | ||
HQUIC l = l_ctx->Listener; | ||
// set before destroy_l_ctx | ||
l_ctx->Listener = NULL; | ||
l_ctx->is_closed = TRUE; | ||
|
||
// If is_stopped, it means the listener is already stopped. | ||
// there will be no callback for QUIC_LISTENER_EVENT_STOP_COMPLETE | ||
// so we need to destroy the l_ctx otherwise it will leak. | ||
is_destroy = l_ctx->is_stopped; | ||
|
||
enif_mutex_unlock(l_ctx->lock); | ||
|
||
// It is safe to close it without holding the lock | ||
// This also ensures no ongoing listener callbacks | ||
// This is a blocking call. @TODO have async version or use dirty scheduler | ||
MsQuic->ListenerClose(l); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is it OK to close the listener if it was already closed? Or should it be better to return just after the critical section if ret != ATOM_OK? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is ok. |
||
if (is_destroy) | ||
{ | ||
destroy_l_ctx(l_ctx); | ||
} | ||
return ret; | ||
} | ||
|
||
ERL_NIF_TERM | ||
stop_listener1(ErlNifEnv *env, | ||
__unused_parm__ int argc, | ||
const ERL_NIF_TERM argv[]) | ||
{ | ||
QuicerListenerCTX *l_ctx; | ||
ERL_NIF_TERM ret = ATOM_OK; | ||
BOOLEAN is_stopped = FALSE; | ||
assert(argc == 1); | ||
if (!enif_get_resource(env, argv[0], ctx_listener_t, (void **)&l_ctx)) | ||
{ | ||
return ERROR_TUPLE_2(ATOM_BADARG); | ||
} | ||
|
||
enif_mutex_lock(l_ctx->lock); | ||
HQUIC l = l_ctx->Listener; | ||
is_stopped = l_ctx->is_stopped; | ||
l_ctx->is_stopped = TRUE; | ||
enif_mutex_unlock(l_ctx->lock); | ||
if (!l) | ||
{ | ||
return ERROR_TUPLE_2(ATOM_CLOSED); | ||
} | ||
else if (!is_stopped) | ||
{ | ||
// void return | ||
MsQuic->ListenerStop(l); | ||
} | ||
return ret; | ||
} | ||
|
||
ERL_NIF_TERM | ||
start_listener3(ErlNifEnv *env, | ||
__unused_parm__ int argc, | ||
const ERL_NIF_TERM argv[]) | ||
{ | ||
ERL_NIF_TERM listener_handle = argv[0]; | ||
ERL_NIF_TERM elisten_on = argv[1]; | ||
ERL_NIF_TERM options = argv[2]; | ||
|
||
QuicerListenerCTX *l_ctx; | ||
unsigned alpn_buffer_length = 0; | ||
QUIC_BUFFER alpn_buffers[MAX_ALPN]; | ||
QUIC_ADDR Address = {}; | ||
int UdpPort = 0; | ||
|
||
// Return value | ||
ERL_NIF_TERM ret = ATOM_OK; | ||
QUIC_STATUS Status = QUIC_STATUS_SUCCESS; | ||
|
||
if (!enif_get_resource( | ||
env, listener_handle, ctx_listener_t, (void **)&l_ctx)) | ||
{ | ||
return ERROR_TUPLE_2(ATOM_BADARG); | ||
} | ||
|
||
char listen_on[INET6_ADDRSTRLEN + 6] = { 0 }; | ||
if (enif_get_string( | ||
env, elisten_on, listen_on, INET6_ADDRSTRLEN + 6, ERL_NIF_LATIN1) | ||
> 0) | ||
{ | ||
if (!(QuicAddr4FromString(listen_on, &Address) | ||
|| QuicAddr6FromString(listen_on, &Address))) | ||
{ | ||
return ERROR_TUPLE_2(ATOM_BADARG); | ||
} | ||
} | ||
else if (enif_get_int(env, elisten_on, &UdpPort) && UdpPort >= 0) | ||
{ | ||
QuicAddrSetFamily(&Address, QUIC_ADDRESS_FAMILY_UNSPEC); | ||
QuicAddrSetPort(&Address, (uint16_t)UdpPort); | ||
} | ||
else | ||
{ | ||
return ERROR_TUPLE_2(ATOM_BADARG); | ||
} | ||
|
||
if (!load_alpn(env, &options, &alpn_buffer_length, alpn_buffers)) | ||
{ | ||
return ERROR_TUPLE_2(ATOM_ALPN); | ||
} | ||
|
||
enif_mutex_lock(l_ctx->lock); | ||
if (!l_ctx->Listener) | ||
{ | ||
ret = ERROR_TUPLE_2(ATOM_CLOSED); | ||
goto exit; | ||
} | ||
|
||
if (QUIC_FAILED( | ||
Status = MsQuic->ListenerStart( | ||
l_ctx->Listener, alpn_buffers, alpn_buffer_length, &Address))) | ||
{ | ||
TP_NIF_3(start_fail, (uintptr_t)(l_ctx->Listener), Status); | ||
ret = ERROR_TUPLE_3(ATOM_LISTENER_START_ERROR, ATOM_STATUS(Status)); | ||
goto exit; | ||
} | ||
l_ctx->is_stopped = FALSE; | ||
|
||
exit: | ||
enif_mutex_unlock(l_ctx->lock); | ||
|
||
return ATOM_OK; | ||
return ret; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,7 +32,10 @@ | |
|
||
%% Traffic APIs | ||
-export([ listen/2 | ||
, stop_listen/1 | ||
, start_listen/3 | ||
, close_listener/1 | ||
, close_listener/2 | ||
, connect/4 | ||
, async_connect/3 | ||
, handshake/1 | ||
|
@@ -168,19 +171,39 @@ reg_open(Profile) -> | |
reg_close() -> | ||
quicer_nif:reg_close(). | ||
|
||
-spec start_listener(Appname :: atom(), listen_on(), | ||
-spec start_listener(Appname :: atom() | listener_handle(), listen_on(), | ||
{listener_opts(), | ||
connection_opts(), | ||
stream_opts() | user_opts()} | ||
) -> | ||
{ok, pid()} | {error, any()}. | ||
start_listener(AppName, Port, Options) -> | ||
start_listener(AppName, Port, Options) when is_atom(AppName) -> | ||
quicer_listener:start_listener(AppName, Port, Options). | ||
|
||
-spec stop_listener(atom()) -> ok. | ||
stop_listener(AppName) -> | ||
-spec start_listen(listener_handle(), listen_on(), listen_opts()) -> | ||
{ok, pid()} | {error, any()}. | ||
start_listen(Listener, Port, Options) when is_list(Options)-> | ||
start_listen(Listener, Port, maps:from_list(Options)); | ||
start_listen(Listener, Port, Options) -> | ||
quicer_nif:start_listener(Listener, Port, Options). | ||
|
||
-spec stop_listener(atom() | listener_handle()) -> ok. | ||
stop_listener(AppName) when is_atom(AppName)-> | ||
quicer_listener:stop_listener(AppName). | ||
|
||
-spec stop_listen(listener_handle()) -> ok. | ||
stop_listen(Handle) -> | ||
case quicer_nif:stop_listener(Handle) of | ||
ok -> | ||
receive | ||
{quic, listener_stopped, Handle} -> | ||
ok | ||
end; | ||
%% @TODO handle already stopped | ||
{error, Reason} -> | ||
{error, Reason} | ||
end. | ||
|
||
%% @doc Start listen on Port or "HOST:PORT". | ||
%% | ||
%% listener_handle() is used for accepting new connection. | ||
|
@@ -202,9 +225,30 @@ listen(ListenOn, Opts) when is_map(Opts) -> | |
quicer_nif:listen(ListenOn, Opts). | ||
|
||
%% @doc close listener with listener handle | ||
-spec close_listener(listener_handle()) -> ok. | ||
-spec close_listener(listener_handle()) -> ok | {error, badarg | closed | timeout}. | ||
close_listener(Listener) -> | ||
quicer_nif:close_listener(Listener). | ||
close_listener(Listener, 5000). | ||
|
||
-spec close_listener(listener_handle(), timer:time()) -> | ||
ok | {error, badarg | closed | timeout}. | ||
close_listener(Listener, Timeout) -> | ||
case quicer_nif:close_listener(Listener) of | ||
ok when Timeout == 0 -> | ||
ok; | ||
ok -> | ||
receive | ||
{quic, listener_stopped, Listener} -> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The event is only shot to the Listener owner who opens the listener. |
||
ok | ||
after Timeout -> | ||
{error, timeout} | ||
end; | ||
{error, closed} -> | ||
%% already closed | ||
%% follow OTP behavior | ||
ok; | ||
{error, _} = E-> | ||
E | ||
end. | ||
|
||
%% @doc | ||
%% Initiate New Connection (Client) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
was the TODO removed on purpose or by accident? (as it's not related to this change)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could use ListenerStop for async calls instead and keep ListenerCLose sync.
maybe it is a good time to add ListenerStop support.
I will update the PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a blocking call but it is cheap, that is why the @todo is removed.