summaryrefslogtreecommitdiffstats
path: root/src/lib/OpenSLX/ScopedResource.pm
blob: af912691735ff155f5273922fd434259cfc98f05 (plain) (blame)
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
# Copyright (c) 2008 - OpenSLX GmbH
#
# This program is free software distributed under the GPL version 2.
# See http://openslx.org/COPYING
#
# If you have any feedback please consult http://openslx.org/feedback and
# send your suggestions, praise, or complaints to feedback@openslx.org
#
# General information about OpenSLX can be found at http://openslx.org/
# -----------------------------------------------------------------------------
package OpenSLX::ScopedResource;

use strict;
use warnings;

our $VERSION = 1.01;        # API-version . implementation-version

=head1 NAME

OpenSLX::ScopedResource - provides a helper class that implements the 
'resource-acquisition-by-definition' pattern.

=head1 SYNOPSIS

{   # some scope

    my $distroSession = OpenSLX::ScopedResource->new({
        name    => 'distro::session',
        acquire => sub { $distro->startSession(); 1 },
        release => sub { $distro->finishSession(); 1 },
    });
    
    die $@ if ! eval {
        # do something dangerous and unpredictable here:
        doRandomStuff();
        1;
    };
        
} 
# the distro-session will be cleanly finished, no matter if we died or not

=head1 DESCRIPTION

The class C<ScopedResource> wraps any resource such that the resource will be
acquired when an object of this class is created. Whenever the ScopedResource
object is being destroyed (e.g. by leaving scope) the wrapped resource will
automatically be released.

The main purpose of this class is to make it simple to implement reliable 
resource acquisition and release management even if the structure of the code
that refers to that resource is rather complex. 

Furthermore, this class handles cases where the script handling those resources 
is spread across different process and/or makes us of signal handlers.

=cut

# make sure that we catch any signals in order to properly release scoped
# resources
use sigtrap qw( die normal-signals error-signals );

use OpenSLX::Basics;

=head1 PUBLIC METHODS

=over

=item B<new($params)>

Creates a ScopedResource object for the resource specified by the given 
I<$params>.

As part of creation of the object, the resource will be acquired.

The I<$params>-hashref requires the following entries:

=over

=item C<name>

Gives a name for the wrapped resource. This is just used in log messages
concerning the acquisition and release of that resource.

=item C<acuire>

Gives the code that is going to be executed in order to acquire the resource.

=item C<release>

Gives the code that is going to be executed in order to release the resource.

=back

=cut

sub new
{
    my $class = shift;
    my $params = shift;
    
    checkParams($params, {
        name    => '!',
        acquire => '!',
        release => '!',
    });

    my $self = {
        name    => $params->{name},
        owner   => 0,
        acquire => $params->{acquire},
        release => $params->{release},
    };
    
    bless $self, $class;
    
    $self->_acquire();
    
    return $self;
}

=item B<DESTROY()>

Releases the resource (if it had been acquired by this process) and cleans up.

=cut

sub DESTROY
{
    my $self = shift;
    
    $self->_release();
    
    # remove references to functions, in order to release any closures
    $self->{acquire} = undef;
    $self->{release} = undef;
    
    return;
}

sub _acquire
{
    my $self = shift;

    # acquire the resource and set ourselves as owner
    if ($self->{acquire}->()) {
        vlog(1, "process $$ acquired resource $self->{name}");
        $self->{owner} = $$;
    }
}

sub _release
{
    my $self = shift;

    # only release the resource if invoked by the owning process
    vlog(3, "process $$ tries to release resource $self->{name}");
    return if $self->{owner} != $$;
    
    # ignore ctrl-c while we are trying to release the resource, as otherwise
    # the resource would be leaked
    local $SIG{INT} = 'IGNORE';

    # release the resource and unset owner
    if ($self->{release}->()) {
        vlog(1, "process $$ released resource $self->{name}");
        $self->{owner} = 0;
    }
}

=back

=cut

1;