Open analogrelay opened 5 years ago
Couple notes from PR reviews:
http_request
timeout - should we have a separate parameter on send instead, or CTS-like class we return from send?
websocket_transport
- "trampoline" instead of recursion in receive loop (would prevent stack overflow from bad code), https://github.com/aspnet/AspNetCore/pull/8420#discussion_r266165339
Threading - Currently relying on the websocket/http stack to do threading, we should decide on who owns threading and how to do it
websocket_client
- on_receive
vs receive
, receive
means we control the loop, on_receive
means websocket_client
providers need to handle the loop
set_disconnected
- the callback should probably accept an exception
We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.
@BrennanConroy are these issues still relevant or are we tracking the client work separately?
final
? (sealed)noexcept
, should we only apply it to ctor/move ctor/copy ctor?
noexcept
and ends up throwing it calls std::terminate
, so we need to be really confident it won't throw (don't trust user code, etc.)noexcept
mainly comes from ctorsnoexcept
does declare intent thoughYOU SHOULD declare all functions that can never throw exceptions noexcept.
API Review Notes:
#include ...
hub_connection_builder.create(string)
vs withUrl()
. create(string)
seems fine.build()
return a pointer or shared_ptr
? We think pointer is fine. You can create a shared_ptr
if you want.hub_connection_builder.create
return a pointer? Does hub_connection_builder
need a copy constructor if it's mostly tracking references so it's not really a copy?with_logging
need a shared_ptr
? It feels safer. Logging can happen in the background.signalr_client_config
and see if we get pushback. If something like proxy support is really needed, we can consider adding support to http_client.h.with_websocket_factory
parameter websocket_factory
instead of factory
.hub_connection_builder
more than once? Not for now.with_websocket_factory
and with_http_client_factory
take std::function<websocket_client*>
/std::function<http_client*>
instead of a shared_ptr
.#ifdef
for with_messagepack_hub_protocol
? Inside of it yes. It's not really discoverable if we remove the public API by default. It will throw at runtime if you're missing the define. But the API is always available.TBC
- Should build() return a pointer or shared_ptr? We think pointer is fine. You can create a shared_ptr if you want.
I would instead return std::unique_ptr<T>
. The ownership of a naked pointer is in question. Providing std::unique_ptr<T>
helps avoid that ambiguity.
make types final? (sealed)
Yes. This improves optimization opportunities.
I'd also recommend referring to https://github.com/isocpp/CppCoreGuidelines.
Taking some time to audit this API and how it works with the GSL might also be worthwhile.
I would instead return
std::unique_ptr<T>
Wouldn't this make hub_connection
a lot harder to use from different threads/places in code? Someone would need to place it in a global/another class and then use it via some methods on their class.
It would make it harder (impossible) to create a reference cycle via lambda captures (std::shared_ptr) though which is nice.
Wouldn't this make hub_connection a lot harder to use from different threads/places in code? Someone would need to place it in a global/another class and then use it via some methods on their class.
Based on the statement, "We think pointer is fine. You can create a shared_ptr if you want.", there is an expectation for users to take a raw pointer and create a shared_ptr<T>
, right? In the current form you are saying "here is a raw pointer with no assumptions about ownership, do as you please". That is wrong because if a user can immediately wrap the raw pointer it implies the pointer is owned by the caller. However, if you provide unique_ptr<T>
, then the caller knows they own it and can do as they please safely. The shared_ptr<T>
ctor accepts an R-value reference of a unique_ptr<T>
specifically to indicate these ownership semantics and does it explicitly.
There should be no reason to use raw pointers at an API level in C++14. In C++11 there are a few cases, but none jumped out to me in the above API. Working through some use cases with the GSL would help with some of this.
The
shared_ptr<T>
ctor accepts an R-value reference of aunique_ptr<T>
Ah, that's helpful.
Working through some use cases with the GSL would help with some of this.
This seems difficult since the repo says it assumes C++14 or higher... we're stuck targeting C++11
This seems difficult since the repo says it assumes C++14 or higher... we're stuck targeting C++11
Oh, I missed that. Sigh... Reviewing the core guidelines is worth at least an hour or two though.
API Review Notes (Cont.):
noexcept;
.hub_connection_builder
methods.std::map
, just pass it by value unless we're processing the data inline.
configure_options(const signalr_client_config& config)
. It should be configure_options(signalr_client_config config)
.hub_connection_builder
, are all copies "built" meaning they cannot be built again?
create(string)
to a ctor and get rid of the copy constructor.http_request
should be read-only and http_response
can be write-only.get_http_headers() const noexcept;
and std::map<std::string, std::string>& get_http_headers() noexcept;
.signalr_client_config
. It cannot because we pass it to the transport by const
.std::map<std::string, std::string>& get_http_headers()
should be removed.scheduler
from const std::shared_ptr<scheduler>& get_scheduler() const noexcept;
being stored? Yes. Let's just make it std::shared_ptr<scheduler> get_scheduler() const noexcept;
. No const references for returns (probably).std::function<std::shared_ptr<websocket_client>(const signalr_client_config&)> websocket_factory
should be using unique_ptr
just like hub_connection_builder.build()
.std::function
by value if you are storing it after the call.hub_connection
since we're now handing out a unique_ptr
.method_invoked_handler
.std::exception_ptr
is a bad API. We could pass a copy of the exception object itself but we'd lose info like the stack trace. Or... std::function<void(std::exception_ptr)> callback
could be std::function<void(const std::exception*)> callback
.__cdecl
.set_disconnected
to on_disconnected
.on
additive? Yes. Let's make it the same for on_disconnected
.on
callbacks? Maybe. We could theoretically add a registration return value later and add an off
method.const std::string&
vs const char*
? const std::string&
is better.API Review Notes:
signalr_value
.*.hpp
instead of *.h
for all header files since we're using C++ even if it is header-only with no code.<
instead of "
for includes.unique_ptr
for with_websocket_factory
and with_http_client_factory
. We're convinced we can make the tests work somehow.signalr_client_config
with noexcept
. Do a noexcept
pass.std::variant
or use it for signalr_value
? No. It's C++14 and greater and the index
API is unnecessary for a custom tagged union.value
constructors since we're just copying in the constructor anyway.value(const char* val)
. It's not too hard to have the user convert to std::string
themselves.value_type.null
is a unique concept because we don't know the type we're extracting to. Given null, is_string
will return false, as_string
will throw, etc... We could return a unique_ptr
from as_string
to return "null", but this seems unwieldy. Custom converters can manually check is_null
for nullable properties.value_type
into value
and rename it to type
.: int
log_level
be none
to be more consistent with C++ libraries? It's probably better to align with Microsoft.Extensions.Logging LogLevel, but we should think about it.trace_level
to log_level
.signalr_client_config
? Or should we force people to wire the logger themselves to custom transports and client?noexcept
.http_method
instead http_request
? Would we rename it to just method
if it's nested? Probably.websocket_client
be turned into an arbitrary transport? It's really close. The registration would have to change so with_websocket_factory
is now called with_transport_factory
and could take transport_type
as the first parameter.std::string
used by http_response
are UTF-8 encoded chars. Can we make it Vector<uint8_t>
? Is there away to avoid copying. If the only callback reading the buffer is internal code, maybe it's okay to just take a pointer and length and promise to do any copying we need in the callback.read_to_end
in the default_websocket_client
!Thanks for contacting us.
We're moving this issue to the .NET 8 Planning
milestone for future evaluation / consideration. We would like to keep this around to collect more feedback, which can help us with prioritizing this work. We will re-evaluate this issue, during our next planning meeting(s).
If we later determine, that the issue has no community involvement, or it's very rare and low-impact issue, we will close it - so that the team can focus on more important and high impact issues.
To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.
Thanks for contacting us.
We're moving this issue to the .NET 8 Planning
milestone for future evaluation / consideration. We would like to keep this around to collect more feedback, which can help us with prioritizing this work. We will re-evaluate this issue, during our next planning meeting(s).
If we later determine, that the issue has no community involvement, or it's very rare and low-impact issue, we will close it - so that the team can focus on more important and high impact issues.
To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.
Epic #5301
We need to do a public API review for the SignalR C++ client to make sure we're happy with it.