Skip to content

Registries mint a permanent member for every unrecognised value: 1,169 extend_enum sites across 113 registries #775

Description

@JarryShaw

Is your feature request related to a problem? Please describe.

Every registry under pcapkit/const/ turns an unrecognised value into a permanently registered enum member via aenum.extend_enum, so a process that parses many distinct unassigned values grows its registries without bound. #575 measured the cost on one path — 111 calls and ~13.5% of extraction self time on http.pcap — but the mechanism is repo-wide.

The maintainer's ruling, verbatim: "i think we should even apply to all other Enum's legit but unbounded values - so that we dont create registered enums out of unrecognised/unregistered values, unless user/caller explicitly created them."

Describe the solution you'd like

Return an unregistered member of the namespace class instead of minting. Measured as viable on AppType:

obj = str.__new__(TCP, 'unassigned')
obj._name_ = 'unassigned'; obj._value_ = 'unassigned'
obj.port = 54321; obj.svc = 'unknown'; obj.proto = TransportProtocol.tcp
repr             : <TCP.unknown: 54321 [tcp]>
isinstance TCP   : True
registry grew    : 6147 -> 6147 members, 6147 -> 6147 values

It satisfies isinstance, reprs identically to a declared member, and does not grow the registry. Note AppType is a StrEnum (TCP → AppType → StrEnum → str → ReprEnum), so it is str.__new__; an int-valued registry needs int.__new__ instead, and int.__new__(TCP, …) raises TypeError: TCP is not a subtype of int.

Scope, measured by AST over pcapkit/const/

extend_enum inside _missing_ : 1056 calls across 113 files
extend_enum inside get()     :  113 calls across 112 files

1,169 sites across 113 registries. Both paths mint per value — including the _missing_ declared-range branches, which is not obvious:

TCP.get(49100)   -> <TCP.unassigned: 49100 [undefined]>   members 6147 -> 6148
TCP.get(49101)   -> <TCP.unassigned: 49101 [undefined]>   members 6148 -> 6149
LinkType(60000)  -> <LinkType.Unassigned_60000: 60000>    members  220 ->  221

Those branches are bounded in which ranges they cover, not in how many members they create.

Additional context

#771 implements this for the four EnumField subclasses only — the parse path, where the measured cost is — and closes #575. This issue is the rest.

Three questions to settle before a wide change, none resolved: a value-lookup like TCP(54321) stays failing since the instance is absent from _value2member_map_; AppType.__eq__/__hash__ key on .port, so two unregistered instances for one port compare equal without being identical; and pickling or copying an unregistered member may not survive. Also open is whether this belongs in each registry's _missing_ — where aenum naturally hooks and where 1,056 of the sites are — rather than at the field layer. The ruling's "unless user/caller explicitly created them" means a direct get() call may still register; only paths that never asked for a name should get the unregistered member.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    breakingBreaks public-facing behaviour or API (apply alongside the type label)designA design or decision issue: a pattern being decided rather than a defect or a requestenhancementperfPull requests that improve performance (perf: subject prefix)wipWork in flight - a covering PR is open or an agent is actively on it

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions