/usr/share/perl5/Rinci/Undo.pod is in librinci-perl 1.1.43-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 | package Rinci::Undo; # just to make PodWeaver happy
# VERSION
1;
# ABSTRACT: (DEPRECATED) Protocol for undo operations in functions
__END__
=pod
=encoding UTF-8
=head1 NAME
Rinci::Undo - (DEPRECATED) Protocol for undo operations in functions
=head1 VERSION
version 1.1.43
=head1 SPECIFICATION VERSION
1.1
=head1 STATUS
This protocol (riundo for short) is now deprecated in favor of
L<Rinci::Transaction> (ritx for short) for several reasons:
=over 4
=item * riundo is inherently unreliable
Undo information is returned by function I<after> the function has performed the
action. If function dies in the middle of action, client does not have the
information to undo the (partially completed) action. That is why in ritx, the
TM asks the function first for undo information before asking the function to
perform its action.
=item * ritx does not limit using the same function for undo
In riundo, we must call the same function (passing the previously obtained undo
data from the that function) to undo the information. This is sometimes slightly
cumbersome. The undo action might be provided by other functions, but we still
have to go through the same function first.
=item * ritx can also implement undo/redo
So there is no need for maintaining two specifications.
=back
=head1 SPECIFICATION
This document describes the Rinci undo protocol. This protocol must be followed
by functions that claim that they support undo (have their C<undo> C<feature>
set to true). Such functions are from here on called I<undoable function> (or
just function, unless when ambiguous).
The protocol is basically the non-OO version of the command pattern, a design
pattern most commonly used to implement undo/redo functionality. In this case,
each function behaves like a command object. You pass a special argument
C<-undo_action> with the value of C<do> and C<undo> to execute or undo a
command, respectively. For C<do> and C<undo>, the same set of arguments are
passed.
=head2 Requirements
Function MUST check special argument C<-undo_action> before it checks other
arguments. Function MUST at least support the following undo action: C<do>,
C<undo>. On unsupported/unknown undo action, function MUST return status 400,
with message like "Unsupported undo action".
If C<-undo_action> is not set, it means caller does not care about undo.
Undoable function should execute as any normal function.
=head2 Performing 'do'
To indicate that we need undo, we call function by passing special argument
C<-undo_action> with the value of C<do>. Function should perform its operation
and save undo data along the way. If C<-undo_action> is not passed or
false/undef, function should assume that caller does not need undo later, so
function need not save any undo data. After completing operation successfully,
function should return status 200, the result, and undo data. Undo data is
returned in the result metadata (the fourth element of result envelope),
example:
[200, "OK", $result, {undo_data=>$undo_data}]
Undo data should be serializable so it is easy to be made persistent if
necessary (e.g. by some undo/transaction manager).
=head2 Performing 'undo'
To perform an undo, caller must call the function again with the same previous
arguments, except C<-undo_action> should be set to C<undo> and C<-undo_data> set
to undo data previously given by the function. Function should perform the undo
operation using the undo data. Upon success, it must return status 200, the
result, and an undo data (in other words, redo data, since it can be used to
undo the undo operation).
=head2 Performing 'redo'
To perform redo, caller can call the function again with <-undo_action> set to
C<undo> and C<-undo_data> set to the redo data given in the undo step. Or,
alternatively, caller can just perform a normal do (see above).
An example:
$SPEC{setenv} = {
v => 1.1,
summary => 'Set environment variable',
args => {
name => {req=>1, schema=>'str*'},
value => {req=>1, schema=>'str*'},
},
features => {undo=>1},
};
sub setenv {
my %args = @_;
my $name = $args{name};
my $value = $args{value};
my $undo_action = $args{-undo_action} // '';
my $undo_data = $args{-undo_data};
my $old;
if ($undo_action) {
# save original value and existence state
$old = [exists($ENV{$name}), $ENV{$name}];
}
if ($undo_action eq 'undo') {
if ($undo_data->[0]) {
$ENV{$name} = $undo_data->[1];
} else {
delete $ENV{$name};
}
} else {
$ENV{$name} = $value;
}
[200, "OK", undef, $undo_action ? {undo_data=>$old} : {}];
}
The above example declares an undoable command C<setenv> to set an environment
variable (C<%ENV>).
To perform command:
my $res = setenv(name=>"DEBUG", value=>1, -undo_action=>"do");
die "Failed: $res->[0] - $res->[1]" unless $res->[0] == 200;
my $undo_data = $res->[3]{undo_data};
To perform undo:
$res = setenv(name=>"DEBUG", value=>1,
-undo_action="undo", -undo_data=>$undo_data);
die "Can't undo: $res->[0] - $res->[1]" unless $res->[0] == 200;
After this undo, DEBUG environment variable will be set to original value. If it
did not exist previously, it will be deleted.
To perform redo:
my $redo_data = $res->[3]{undo_data};
$res = setenv(name=>"DEBUG", value=>1,
-undo_action="undo", -undo_data=>$redo_data);
or you can just do:
$res = setenv(name=>"DEBUG", value=>1, -undo_action="do");
=head2 Saving undo data in external storage
Although the complete undo data can be returned by the function in the
C<undo_data> result metadata property, sometimes it is more efficient to just
return a pointer to said undo data, while saving the actual undo data in some
external storage.
For example, if a function deletes a big file and wants to save undo data, it is
more efficient to move the file to trash directory and return its path as the
undo data, instead of reading the whole file content and its metadata to memory
and return it in C<undo_data> result metadata.
Functions which require undo trash directory should specify this in its
metadata, through the C<undo_trash_dir> dependency clause. For example:
deps => {
...
trash_dir => 1,
}
When calling function, caller needs to provide path to undo trash directory via
special argument C<-trash_dir>, for example:
-trash_dir => "/home/.trash/2fe2f4ad-a494-0044-b2e0-94b2b338056e"
=head2 What about non-undoable actions?
Like in real life, not all actions are undoable. Examples of
undoable/irreversible actions include wiping a file/directory (more generally
speaking, any action to permanently delete/destroy something, without backing up
the data first), sending an email (more generally speaking, any action that is
sent to an external entity beyond our control, unless that external entity
provides a way to undo the action).
An undoable function MUST NOT mix undoable and non-undoable actions. For
example:
safe_delete(file=>'/path/to/file'); # puts file into Trash, undoable action
safe_delete(file=>'/path/to/file', permanent=>1); # deletes file, non-undoable
The C<safe_delete> function above mixes undoable action (putting a file into
Trash directory) and non-undoable action (permanently deleting a file without
putting it in Trash). Without domain knowledge of the function, a caller cannot
know whether a call will be undoable or not. This will also prevent the function
from participating in a transaction, because transaction requires function call
to always be undoable, for rollback purpose.
The solution is to separate non-undoable action in another function, for
example:
trash(file=>'/path/to/file'); # undoable, can execute inside transaction
delete(file=>'/path/to/file'); # non-undoable, executes outside transaction
empty_trash(); # non-undoable, executes outside transaction
The non-undoable function is also non-transactional (it operates outside the
scope of a transaction). But it can still be idempotent. And it can manipulate
the transactions if it needs too. In the example, the empty_trash() function
instructs the transaction manager to discard the trash() transactions, since
after the trash is emptied, the trash() transactions cannot be undone anyway.
=head1 SEE ALSO
Related specifications: L<Rinci::Transaction>
=head1 HOMEPAGE
Please visit the project's homepage at L<https://metacpan.org/release/Rinci>.
=head1 SOURCE
Source repository is at L<https://github.com/sharyanto/perl-Rinci>.
=head1 BUGS
Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Rinci>
When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.
=head1 AUTHOR
Steven Haryanto <stevenharyanto@gmail.com>
=head1 COPYRIGHT AND LICENSE
This software is copyright (c) 2013 by Steven Haryanto.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut
|