ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MCP/lib/AnyEvent/MCP/Workdirs.pm
Revision: 1.1
Committed: Thu Oct 15 09:07:04 2026 UTC (2 weeks, 2 days ago) by root
Branch: MAIN
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 =head1 NAME
2    
3     AnyEvent::MCP::Workdirs - persistent filesystem workdir IDs
4    
5     =head1 DESCRIPTION
6    
7     The registry assigns small positive integers to resolved absolute workdir
8     paths. It is shared by filesystem server instances using the same file.
9     Permission-rule identities remain independent of these selectors.
10    
11     =head1 METHODS
12    
13     =over
14    
15     =item $registry = AnyEvent::MCP::Workdirs->new (file => $file, paths => [$path...], expiry => $seconds)
16    
17     Resolve IDs for the supplied resolved paths, reserving new allocations under
18     the file lock. C<expiry> defaults to seven days. C<ids> contains the assigned
19     IDs in path order and stays fixed for this object's lifetime. The caller
20     refreshes immediately and periodically while its server is running.
21    
22     =item $registry->refresh
23    
24     Lock and reread the JSON registry, overwrite this server's assigned IDs with
25     their paths and the current C<last_use>, expire old entries after C<expiry>
26     seconds, and atomically replace the file. This never resolves or reallocates
27     the server's IDs. Startup allocations reuse the smallest available positive
28     IDs; configured paths retain their previous IDs if nobody reused them.
29    
30     Entries are C<ID =E<gt> [workdir, last_use]>. The JSON uses UTF-8 with pretty printing.
31     Manual changes require restarting filesystem servers using the registry.
32    
33     =back
34    
35     =cut
36    
37     package AnyEvent::MCP::Workdirs;
38    
39     use common::sense;
40     use AnyEvent::MCP ();
41     use AnyEvent::MCP::File ();
42     use JSON::XS ();
43    
44     our @ISA = qw(AnyEvent::MCP::Base);
45    
46     my $json = JSON::XS->new->utf8->canonical->pretty;
47    
48     sub _read {
49     my ($self, $lock) = @_;
50    
51     local $/;
52     my $text = <$lock>;
53     my $entries = length $text ? $json->decode ($text) : {};
54     ref $entries eq "HASH" or die "$self->{file}: expected workdir ID object\n";
55    
56     for my $id (keys %$entries) {
57     my $entry = $entries->{$id};
58     $id =~ /\A[1-9][0-9]*\z/ && ref $entry eq "ARRAY" && @$entry == 2
59     && defined $entry->[0] && !ref $entry->[0] && $entry->[0] =~ m{\A/} && $entry->[0] !~ /\0/
60     && defined $entry->[1] && !ref $entry->[1] && $entry->[1] =~ /\A[0-9]+\z/
61     or die "$self->{file}: invalid workdir entry '$id'\n";
62     }
63    
64     $entries
65     }
66    
67     sub new {
68     my ($class, %kv) = @_;
69    
70     my $self = $class->SUPER::new (expiry => 7 * 86400, %kv);
71     my $lock = AnyEvent::MCP::File::lock $self->{file};
72     my $entries = $self->_read ($lock);
73     my $now = time;
74     my %active = map { $_ => 1 } @{$self->{paths}};
75    
76     for my $id (keys %$entries) {
77     delete $entries->{$id}
78     unless $active{$entries->{$id}[0]} || $entries->{$id}[1] > $now - $self->{expiry};
79     }
80    
81     my %id = map { $entries->{$_}[0] => $_ } keys %$entries;
82     my @ids;
83     my $next = 1;
84    
85     for my $path (@{$self->{paths}}) {
86     unless (exists $id{$path}) {
87     ++$next while exists $entries->{$next};
88     $id{$path} = $next++;
89     $entries->{$id{$path}} = [$path, $now];
90     }
91    
92     push @ids, 0 + $id{$path};
93     }
94    
95     # reserve allocations before another server can choose the same IDs.
96     AnyEvent::MCP::File::replace $self->{file}, $json->encode ($entries);
97     $self->{ids} = \@ids;
98    
99     $self
100     }
101    
102     sub refresh {
103     my ($self) = @_;
104    
105     my $lock = AnyEvent::MCP::File::lock $self->{file};
106     my $entries = $self->_read ($lock);
107     my $now = time;
108    
109     for (0 .. $#{$self->{paths}}) {
110     $entries->{$self->{ids}[$_]} = [$self->{paths}[$_], $now];
111     }
112    
113     for my $id (keys %$entries) {
114     delete $entries->{$id} if $entries->{$id}[1] <= $now - $self->{expiry};
115     }
116    
117     AnyEvent::MCP::File::replace $self->{file}, $json->encode ($entries);
118     }
119    
120     1;