Skip to content

Conversation

@NinoFloris
Copy link
Member

@NinoFloris NinoFloris commented Jun 21, 2023

Just a little monster PR 😄

There is a ton to do still but I want to start doing the remainder of the work in public.

Let's start off with some general context, we're working to get Npgsql in a good state for NativeAOT, which means no reflection and ideally a small binary footprint. We've gotten quite far with the former, though things like arrays currently flat out do not work under nativeaot as we are not rooting the types correctly.

The footprint issue however is a lot more intricate. First off is that our handler architecture relies on GVMs, generic virtual methods, which are a huge no-no for footprint (don't trust me, read it from the experts)
image

Secondly we're throwing around async statemachines everywhere, as a result each valuetype T that we implement creates a huge amount of bloat, I've touched on this here in more detail dotnet/runtime#83902

And finally as we're coupling the concept of a postgres type to a single handler type we are unable to split handlers up by clr types it supports, making it difficult to create smaller baselines without having to create a new bespoke set of handlers.

A good example is our json support, we always support reading and writing strings but each serializer has their own handler that adds support for more types. Additionally if somebody wanted to use both STJ and Newtonsoft for whatever reason they would be responsible for creating a composite of those handlers again, and so on.

Another result of this approach is the inability to selectively opt into streaming support, a handler always supports async io, just in case one of the INpgsqlTypeHandler implementations needs it, and as a result we always pay for the size bloat.

The design we have today made a lot of sense when it was created but with trimming and AOT having become a thing we have some redesigning to do and that's what this PR hopes to bring.

We're just at the point where the general shape looks good and the main project + tests compile. It's fleshed out enough that everybody should be able to make sense of the direction this is going in, after which it's a good moment to discuss the finer points.

The next step for me is to get some first tests to pass, it shouldn't be far off but it needs PgReader/PgWriter to be actually implemented on top of NpgsqlRead/WriteBuffer.

Afterwards I have a tentative list to go through (help appreciated when we're at this point!):

  • Reimplement all the missing type handlers (json/ranges/geometry/etc) as converters and resolvers
  • Reimplement the plugins
  • Check all the type mapping business
  • Check binary import/export with resolver based types (DateTime specifically) as we don't have pg types to rely on
  • Decide on the baseline support of NpgsqlSlimDataSourceBuilder (with/without arrays, with/without int to int8 conversions etc)
  • Test perf and fix any shortcomings (I've tried not to do stupid things, but who knows)
  • Implement DbParameter.Size truncate for strings and byte[]
  • Change docs as we now support nullable value types in generic db parameters https://www.npgsql.org/doc/basic-usage.html#strongly-typed-parameters

Closes #4340, closes #4341, closes #3898, closes #4970, closes #5099, closes #5009, closes #4988, closes #5125, closes #4893, closes #4543, closes #3782, closes #3775, closes #3656, closes #3648, closes #1965, closes #2013, closes #2355, closes #3639, closes #3679, closes #4663, closes #4711, closes #4870, closes #4862, closes #5004, closes #4988, closes #5141, closes #5136, closes #5139, closes #5142, closes #5143, closes #5210, closes #5137, closes #4626

@NinoFloris NinoFloris marked this pull request as draft June 21, 2023 18:00
@NinoFloris NinoFloris force-pushed the handler-rework branch 2 times, most recently from 16b8193 to be9cfe4 Compare June 22, 2023 22:53
Copy link
Member

@roji roji left a comment

Choose a reason for hiding this comment

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

Here's a pack of review comments, will continue iterating and submit more soon. Some of these comments are probably just me mis-understanding the new stuff.

@vonzshik of course you're also welcome to review

@NinoFloris NinoFloris force-pushed the handler-rework branch 3 times, most recently from d395ef6 to 5389a54 Compare June 26, 2023 18:00
@NinoFloris NinoFloris marked this pull request as ready for review June 28, 2023 16:27
@NinoFloris NinoFloris force-pushed the handler-rework branch 14 times, most recently from 0ef77f7 to 6898385 Compare July 1, 2023 14:02
Copy link
Contributor

@vonzshik vonzshik left a comment

Choose a reason for hiding this comment

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

I have a general grasp on architecture and I'm more or less ok with it. There might be some small problems or nits (like missing sealed or some other things) but they can be fixed at a later date. While I haven't looked through every single file I'll try to do that after PR is merged.

@@ -52,6 +53,7 @@ public sealed class NpgsqlDataReader : DbDataReader, IDbColumnSchemaGenerator
internal ReaderState State = ReaderState.Disposed;

internal NpgsqlReadBuffer Buffer = default!;
Copy link
Contributor

Choose a reason for hiding this comment

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

What about setting it to connector's buffer in the constructor?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not sure, I haven't touched this line.

Copy link
Contributor

@vonzshik vonzshik Sep 24, 2023

Choose a reason for hiding this comment

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

Reason being, you added a ReferenceEqual check to NpgsqlDataReader to replace the buffer if it differs from the one connector has. My thoughts were that it might help pgo a bit to assume that the buffer almost never changes.

Copy link
Member Author

Choose a reason for hiding this comment

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

I added the check so we avoid the write barrier of unconditionally setting the buffer reference. Afaik PGO cannot assume anything that isn't a static readonly will stay constant (or do anything fancy to optimize a reference check).

Copy link
Contributor

Choose a reason for hiding this comment

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

I added the check so we avoid the write barrier of unconditionally setting the buffer reference. Afaik PGO cannot assume anything that isn't a static readonly will stay constant (or do anything fancy to optimize a reference check).

Yeah, currently it can reason about the implementation of the interface (testing first again the most probable), don't think there is anything what I described yet (doesn't mean it will not be implemented at some point).
Anyway, that's more of a suggestion (we don't really lose anything from it) and I'm okay not doing it.

}

protected override void WriteCore(PgWriter writer, NpgsqlCidr value)
=> NpgsqlInetConverter.WriteImpl(writer, (value.Address, value.Netmask), isCidr: false);
Copy link
Contributor

@MaceWindu MaceWindu Oct 15, 2023

Choose a reason for hiding this comment

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

shouldn't it be isCidr: true?

Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, looks like a mistake - submitted #5338, thanks @MaceWindu. It seems that this is actually ignored on the PG side (there's a comment that says so at least), but definitely good to correct this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

6 participants