Skip to content
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

add support for setting protocol handlers with {.raises.} annotation #1064

Merged
merged 8 commits into from
Mar 28, 2024

Conversation

etan-status
Copy link
Contributor

All of the internal protocol handlers are restricted to {.raises.} of [CancelledError]. However, the LPProtoHandler type is part of the public interface, and example / test code raises more errors than just [CancelledError]. The caller is aware of that and CatchableError is caught.

To allow annotating the internal handlers with the proper {.raises.} annotation, support for an extra LPProtoHandler2 is added. This is backward compatible as the old LPProtoHandler is still allowed. Examples still compile fine. There is one test that needs a slight adjustment as it accesses the internal handler field directly. That field needs to be renamed so that the template is used instead.

Eventually, LPProtoHandler may be phased out, with appropriate notices to users who define custom handlers and the migration requiring errors to be handled inside the handler instead of raising them. At this time, such a deprecation is not yet applied, especially while the internal logic is still relying on the previous handler flow.

All of the internal protocol handlers are restricted to `{.raises.}` of
`[CancelledError]`. However, the `LPProtoHandler` type is part of the
public interface, and example / test code raises more errors than just
`[CancelledError]`. The caller is aware of that and `CatchableError` is
caught.

To allow annotating the internal handlers with the proper `{.raises`.}
annotation, support for an extra `LPProtoHandler2` is added. This is
backward compatible as the old `LPProtoHandler` is still allowed.
Examples still compile fine. There is one test that needs a slight
adjustment as it accesses the internal `handler` field directly. That
field needs to be renamed so that the `template` is used instead.

Eventually, `LPProtoHandler` may be phased out, with appropriate notices
to users who define custom handlers and the migration requiring errors
to be handled inside the handler instead of raising them. At this time,
such a deprecation is not yet applied, especially while the internal
logic is still relying on the previous handler flow.
Copy link

codecov bot commented Mar 11, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 82.56%. Comparing base (08a48fa) to head (417922c).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff            @@
##           unstable    #1064   +/-   ##
=========================================
  Coverage     82.56%   82.56%           
=========================================
  Files            91       91           
  Lines         15814    15816    +2     
=========================================
+ Hits          13057    13059    +2     
  Misses         2757     2757           
Files Coverage Δ
libp2p/multistream.nim 93.71% <ø> (ø)
libp2p/protocols/protocol.nim 100.00% <100.00%> (ø)

conn: Connection,
proto: string): Future[void] {.async.}

LPProtoHandler2* = proc (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm..

not that I necessarily disagree with this change, but this becomes an odd case which should have been solved by covariance - ie technically, because we allow any exception to be raised, we should not constrain here to [CancelledError] (raises: [ValueError] would also be "correct")..

I wonder if there's a way to solve this with some generics, ie create a new that takes an new[E](handler: proc(): InternalRaisesFuture[void, E] - if that's possible, it's probably something we should formalize in chronos somehow, to allow expressing it without referencing Internal..

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, it would still involve going through the new or handler= instead of constructing the object manually. Agree that making it more generic would also serve the role of allowing {.async: raises.} annotation, but for now would have to go with Internal. I guess that's alright to get started.

@@ -22,9 +22,9 @@ type
conn: Connection,
proto: string): Future[void] {.async.}

LPProtoHandler2* = proc (
LPProtoHandler2*[E] = proc (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does lp2[E] = proc ...: Future[void].Raising(E) work?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the handler= implementation worked for me.

I tested it by changing protocols/connectivity/relay/client.nim to

proc handleStream(conn: Connection, proto: string) {.async: (raises: [CancelledError]).} =
  ...
cl.handler = handleStream

and NIMFLAGS="--mm:refc" nimble test still worked fine (refc because on orc it segfaults in metrics unrelated).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, I mean you don't need to expose InternalRaisesFuture here (we can avoid it here too probably: #1050 (comment)):

import chronos
type T[E] = Future[void].Raising(E)
proc x() {.async: (raises: [ValueError]).} = discard
proc f[E](handler: proc(): T[E]) = debugEcho type(handler)

f(x)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the proc takes parameters and is passed around, hose parts would have to be continuously repeated. putting the [E] directly on the proc doesn't work, only the result type can be done that way.

Exposing InternalRaisesFuture in a single place seems like the lesser evil to me, it's trivial to adjust if necessary

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO adding any *2 to the codebase isn't a good software engineering practice as it isn't sufficiently descriptive. Also, why is an Ineternal* type from another project referenced here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have never seen such naming used in mainstream libraries.

The most used and popular api on earth does this, more or less: https://learn.microsoft.com/en-us/windows/win32/api/winver/nf-winver-getfileversioninfoexa (though they call it Ex instead of 2)

I understand your concern, but sometimes, there just isn't any good naming solution and indeed, a comment might be the best option.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with a cherry on top for 3 in the interface name ;) in fact, com interfaces / all of .NET often versions itself this way

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I knew I'd regret the comment, but never worked with MS tho.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So .. thinking a bit more about this, the answer is actually simple: we can remove LPProtocolHandler/LPProtoHandler2 altogether and just use Future[...].Raising(E) explicitly in the wrapper, which ensures user code is not polluted with a name that we don't necessarily want to support.

This makes sense also because there as limitations to how you can use aliases together with `` ie var f: LPProtocolHandler[[ValueError]] doesn't work because of macro instantiation order issues.

Comment on lines 25 to 27
LPProtoHandler2*[E] = proc (
conn: Connection,
proto: string): InternalRaisesFuture[void, E]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
LPProtoHandler2*[E] = proc (
conn: Connection,
proto: string): InternalRaisesFuture[void, E]
LPProtoHandler2*[E] = proc (
conn: Connection,
proto: string): Future[void].Raising(E)

this does not work?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope, not on the proc, only on the result


LPProtocol* = ref object of RootObj
codecs*: seq[string]
handler*: LPProtoHandler ## this handler gets invoked by the protocol negotiator
handlerImpl: LPProtoHandler ## invoked by the protocol negotiator
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this rename breaks LPProtocol(handler: ...) - not sure it's worth it: new code can use LPProtocol.new to get the covariance handling.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's what I mentioned above. It has to be renamed otherwise handler= doesn't take precedence, and handler= is used a lot, while using LPProtocol(...) over LPProtocol.new was only used in 1 example and 1 test, where it seems to be not intentional.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

breaks nimbus-eth2 though

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 liner to fix

etan-status added a commit to status-im/nimbus-eth2 that referenced this pull request Mar 21, 2024
Avoid potenial issue with vacp2p/nim-libp2p#1064 (comment)
in a future dependency bump.
conn: Connection,
proto: string): Future[void] {.async.}

# Callbacks that are annotated with `{.async: (raises).}` explicitly
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks great, thanks.

etan-status added a commit to status-im/nimbus-eth2 that referenced this pull request Mar 21, 2024
Avoid potenial issue with vacp2p/nim-libp2p#1064 (comment)
in a future dependency bump.
@arnetheduck arnetheduck merged commit 03f67d3 into unstable Mar 28, 2024
9 checks passed
@arnetheduck arnetheduck deleted the dev/etan/ex-handlersupport branch March 28, 2024 08:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: done
Development

Successfully merging this pull request may close these issues.

None yet

3 participants