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

Internal exceptions being thrown #555

Closed
daveaglick opened this issue Sep 27, 2021 · 7 comments · Fixed by #620
Closed

Internal exceptions being thrown #555

daveaglick opened this issue Sep 27, 2021 · 7 comments · Fixed by #620
Assignees
Labels
bug Something isn't working
Milestone

Comments

@daveaglick
Copy link
Contributor

I updated Spectre.Console and started seeing handled exceptions being thrown, got curious, and traced it back to PR #376. The problem is that while the new try/catch introduced in this PR around the resolver.Resolve(settingsType) call works, it ends up creating an expected control flow where throwing is "normal". That's fine as far as it goes, but it does result in unnecessary exceptions under the hood which will both impact performance (though likely not in any critical way) and trigger exception filters when debugging. And it feels icky :)

The reason I didn't see these exceptions before the update was because TypeRegistrarExtensions.RegisterDependencies() was registering the settings types in my container for me, so when the old code asked the container for them, they were already in there. That code was removed in this PR so the container doesn't have the settings type registered any more.

It's unclear whether this is a "problem" that needs solving. I generally don't love throwing exceptions during expected flows, even if I'm catching them. If there's an interest in changing the behavior the best thing I can think of is to introduce an ITypeResolver.TryResolve() that can return a null value for unregistered types without throwing (or alternatively use a bool/out pattern). Then use that to resolve the settings type without a try/catch and fallback to direct instantiation if it returns null.

Would you be interested in a PR for this or should the behavior be considered by design?

@daveaglick daveaglick added the bug Something isn't working label Sep 27, 2021
@patriksvensson
Copy link
Contributor

@daveaglick I would want us to do the right thing here, so a PR would be appreciated!

@daveaglick
Copy link
Contributor Author

Will do! Shouldn't be a tough one (famous last words). I may not have a chance to open a PR until Friday for...errr...reasons...

image

👀

@nils-a
Copy link
Contributor

nils-a commented Oct 30, 2021

@daveaglick you're still working on this one? (I mean, there's still a day left in your self-chosen timeframe, so I wouldn't want to put any pressure on you. 😬)

@daveaglick
Copy link
Contributor Author

Lol. I like to live on the edge. Nothing like a little pressure!

@daveaglick
Copy link
Contributor Author

I was all set to implement this as described (only two months later, nbd!) but after digging in, realized that maybe this is just a documentation issue. Consider this code I found in CommandConstructorBinder:

var value = resolver.Resolve(parameter.ParameterType);
if (value == null)
{
    throw CommandRuntimeException.CouldNotResolveType(parameter.ParameterType);
}

That strongly suggests that the expected behavior of ITypeResolver.Resolve() should be to return null when the type cannot be resolved. Compare that to the code I was originally looking at in CommandPropertyBinder:

try
{
    if (resolver.Resolve(settingsType) is CommandSettings settings)
    {
        return settings;
    }
}
catch
{
    // ignored
}

That code has a try/catch guard, suggesting ITypeResolver.Resolve() could throw.

So my question is this: should ITypeResolver.Resolve() work more like the .NET IServiceProvider.GetRequiredService() and guarantee a result or it's an exception or should it work more like IServiceProvider.GetService() and return a null result when the type can't be resolved?

I had assumed the former and implemented my own ITypeResolver that way, which is why it was throwing here. However, if the intent is the latter, then it's my implementation that's wrong and we probably just need to document that better (and remove the try/catch in CommandPropertyBinder because if that's the case then an exception here really is an unexpected exception).

nils-a added a commit to nils-a/spectre.console that referenced this issue Nov 13, 2021
and does not throw on unresolvable types.
Also changed the TypeResolverAdapter to adhere
to those expectations and removed the now no longer
needed try-catch from CommandPropertyBinder.
nils-a added a commit to nils-a/spectre.console that referenced this issue Nov 13, 2021
as a very simple test harness to test implementations of
ITypeRegistrar / ITypeResolver
nils-a added a commit to nils-a/spectre.console that referenced this issue Nov 13, 2021
as a very simple test harness to test implementations of
ITypeRegistrar / ITypeResolver
patriksvensson pushed a commit that referenced this issue Nov 14, 2021
and does not throw on unresolvable types.
Also changed the TypeResolverAdapter to adhere
to those expectations and removed the now no longer
needed try-catch from CommandPropertyBinder.
patriksvensson pushed a commit that referenced this issue Nov 14, 2021
as a very simple test harness to test implementations of
ITypeRegistrar / ITypeResolver
@nils-a nils-a added this to the 0.43 milestone Nov 14, 2021
@nils-a
Copy link
Contributor

nils-a commented Nov 14, 2021

@daveaglick not throwing would be the expectation. I tried to make this clearer in #620.
I also added the TypeRegistrarBaseTest in Spectre.Console.Testing that can be used by someone implementing an ITypeRegistrar/ ITypeResolver to verify some base expectations of these two.

Closed in #620.

@nils-a nils-a closed this as completed Nov 14, 2021
@nils-a nils-a self-assigned this Nov 14, 2021
@daveaglick
Copy link
Contributor Author

Looks great, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
Status: Done 🚀
Development

Successfully merging a pull request may close this issue.

3 participants