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

Migrate away from Moose #1319

Merged
merged 12 commits into from
Apr 10, 2024
Merged

Migrate away from Moose #1319

merged 12 commits into from
Apr 10, 2024

Conversation

mattias-p
Copy link
Member

@mattias-p mattias-p commented Jan 19, 2024

Purpose

This PR replaces the last remaining uses of Moose in Engine.

Context

Fixes #1318.

Changes

  • In most cases Moose is replaced with the more light-weight Class::Accessor. But in some cases Class::Accessor doesn't offer much in the way of replacement. In those cases the Moose constructs are replaced with plain Perl.
  • The dependency on Moose is removed.

How to test this PR

Unit tests should pass.

@tgreenx tgreenx added this to the v2024.1 milestone Jan 25, 2024
Copy link
Contributor

@marc-vanderwal marc-vanderwal left a comment

Choose a reason for hiding this comment

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

Looks good to me so far. I hope this change will improve the engine’s performance somewhat.

@mattias-p
Copy link
Member Author

I ran the unit tests ten times each on b1d6f90 (current develop) 4e0a578 (current 1318-demoose) and found that the minimum running time decreased by about 10% and the average running time decreased by about 9%.

@mattias-p mattias-p marked this pull request as ready for review March 6, 2024 12:17
@mattias-p
Copy link
Member Author

I ran the unit tests once each for the same commits and found that the memory usage decreased by about 18%. ("Maximum resident set size" as reported by GNU time)

@mattias-p
Copy link
Member Author

Also the number of transitive dependencies decrease by about 28% from 67 to 48. (According to cpanm --scandeps --self-contained)

lib/Zonemaster/Engine/Packet.pm Outdated Show resolved Hide resolved
Comment on lines 39 to 52
sub type {
my ( $self ) = @_;
return $self->packet->type;
}

sub string {
my ( $self ) = @_;
return $self->packet->string;
}

sub data {
my ( $self ) = @_;
return $self->packet->data;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

It’s a lot of repetition, it’s true.

Out of curiosity, I tried looking for a solution that involved some metaprogramming to get rid of the repetition. The following code seems to work (well, at least, unit tests pass):

BEGIN {
    # This block uses metaprogramming techniques to define a number of almost
    # identical methods that are passthrough functions to methods in
    # Zonemaster::LDNS::Packet to be called on the `packet` member of
    # instances of this class.

    no strict 'refs';

    # Accessors taking an optional argument, defaulting to an empty list.
    for my $m (qw(timestamp querytime id opcode rcode edns_version)) {
        *{$m} = sub {
            my ( $self, $value ) = @_;
            return $self->packet->$m( $value // () );
        };
    }

    # Accessors that do not take any arguments
    for my $m (qw(type string data
                  aa ra tc do
                  question answer authority additional
                  edns_size edns_rcode edns_z edns_data has_edns)) {
        *{$m} = sub {
            my ( $self ) = @_;
            return $self->packet->$m;
        }
    }
}

I know that touching the symbol table like this is very akin to dark magic in Perl and may scare away newcomers, so I’m also fine with keeping the code as it is, even with the repetition.

Copy link
Member Author

@mattias-p mattias-p Mar 7, 2024

Choose a reason for hiding this comment

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

If the repeated contents was in any way error prone I believe I'd prefer the DRY version. But in this case I feel the repeated version is boring in a good way whereas the DRY version is slightly exciting in a not-so-good way.

Since you went to the effort to come up with an alternative implementation I tried to come up with a third solution. How do you like this?

sub timestamp    { my ( $self, $time )    = @_; return $self->packet->timestamp( $time       // () ); }
sub querytime    { my ( $self, $value )   = @_; return $self->packet->querytime( $value      // () ); }
sub id           { my ( $self, $id )      = @_; return $self->packet->id( $id                // () ); }
sub opcode       { my ( $self, $string )  = @_; return $self->packet->opcode( $string        // () ); }
sub rcode        { my ( $self, $string )  = @_; return $self->packet->rcode( $string         // () ); }
sub edns_version { my ( $self, $version ) = @_; return $self->packet->edns_version( $version // () ); }

sub type       { my ( $self ) = @_; return $self->packet->type; }
sub string     { my ( $self ) = @_; return $self->packet->string; }
sub data       { my ( $self ) = @_; return $self->packet->data; }
sub aa         { my ( $self ) = @_; return $self->packet->aa; }
sub do         { my ( $self ) = @_; return $self->packet->do; }
sub ra         { my ( $self ) = @_; return $self->packet->ra; }
sub tc         { my ( $self ) = @_; return $self->packet->tc; }
sub question   { my ( $self ) = @_; return $self->packet->question; }
sub authority  { my ( $self ) = @_; return $self->packet->authority; }
sub answer     { my ( $self ) = @_; return $self->packet->answer; }
sub additional { my ( $self ) = @_; return $self->packet->additional; }
sub edns_size  { my ( $self ) = @_; return $self->packet->edns_size; }
sub edns_rcode { my ( $self ) = @_; return $self->packet->edns_rcode; }
sub edns_data  { my ( $self ) = @_; return $self->packet->edns_data; }
sub edns_z     { my ( $self ) = @_; return $self->packet->edns_z; }
sub has_edns   { my ( $self ) = @_; return $self->packet->has_edns; }

Copy link
Contributor

Choose a reason for hiding this comment

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

Well, you know the saying in Perl: TIMTOWTDI. I like the way this code is indented, and it’s the way I’ve been doing simple accessors like these in my personal projects.

So yeah, you have a point: there is little gain to be had in maintainability to try to make it DRY. That’s why I said I was fine with the non-DRY version.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think the more compressed alternative that you @mattias-p propose above is better because it is easier to read, and I prefer that to the more magic solution that you @marc-vanderwal suggests. The explicit solution is easier to read and follow.

Copy link
Member Author

Choose a reason for hiding this comment

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

Apparently I forgot to push the commit I made for this, but I pushed it now.

mattias-p and others added 2 commits March 7, 2024 18:11
Co-authored-by: Marc van der Wal <103426270+marc-vanderwal@users.noreply.github.com>
@mattias-p
Copy link
Member Author

@matsduf, @tgreenx, @marc-vanderwal please review again.

@matsduf
Copy link
Contributor

matsduf commented Apr 8, 2024

@mattias-p, you write "Unit tests should pass". Do you think there is enough unit test coverage for this change? This change is hard to review. Make sure that all unit tests work is probably the best test. Could more unit tests be added to verify the correctness of the change?

I think this should be merged soon so that we have a chance to let pass through tests that we do.

I am prepared to approve.

@marc-vanderwal
Copy link
Contributor

marc-vanderwal commented Apr 9, 2024

@mattias-p, you write "Unit tests should pass". Do you think there is enough unit test coverage for this change? This change is hard to review. Make sure that all unit tests work is probably the best test. Could more unit tests be added to verify the correctness of the change?

I checked out the PR’s branch and ran the test suite through Devel::Cover. According to the report for the Zonemaster::Engine::Packet module (originally HTML, had to print to PDF to be able to upload it to Github), there are a handful of untested methods:

  • querytime;
  • id;
  • opcode;
  • data;
  • edns_data;
  • edns_z.

The querytime and data methods are only exercised when the Redis cache is in use. The edns_data method is used in Nameserver11 and edns_z is used in Nameserver12, but the code paths don’t seem to be covered. The remaining methods, id and opcode, are entirely unused.

@mattias-p
Copy link
Member Author

I added a unit test for the mentioned methods without test coverage. I considered removing the unused id and opcode methods instead of adding tests for them, but they are documented and I didn't want to break the API. The new test's aren't very interesting, but I did find a nicer way to integrate Test::NoWarnings and it's nice to have a file where you can just add more unit tests for the Engine Packet module.

@mattias-p mattias-p merged commit e251bc1 into zonemaster:develop Apr 10, 2024
3 checks passed
@tgreenx tgreenx linked an issue Apr 30, 2024 that may be closed by this pull request
@mattias-p mattias-p added the V-Patch Versioning: The change gives an update of patch in version. label Jun 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
V-Patch Versioning: The change gives an update of patch in version.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Remove dependency on Moose
4 participants