1############################################################################
2#
3# Apache::Session::Lock::Semaphore
4# IPC Semaphore locking for Apache::Session
5# Copyright(c) 1998, 1999, 2000 Jeffrey William Baker (jwbaker@acm.org)
6# Distribute under the Perl License
7#
8############################################################################
9
10package Apache::Session::Lock::Semaphore;
11
12use strict;
13use Config;
14use IPC::SysV qw(IPC_PRIVATE IPC_CREAT S_IRWXU SEM_UNDO);
15use IPC::Semaphore;
16use Carp qw/croak confess/;
17use vars qw($VERSION);
18
19$VERSION = '1.04';
20
21BEGIN {
22
23    if ($Config{'osname'} eq 'linux') {
24        #More semaphores on Linux means less lock contention
25        $Apache::Session::Lock::Semaphore::nsems = 32;
26    } elsif ($Config{'osname'}=~/bsd/i) {
27        $Apache::Session::Lock::Semaphore::nsems = 8; #copied from IPC::Semaphore/sem.t minus 1
28    } else {
29        $Apache::Session::Lock::Semaphore::nsems = 16;
30    }
31
32    $Apache::Session::Lock::Semaphore::sem_key = 31818;
33}
34
35sub new {
36    return unless $Config{d_semget};
37    return
38        if $^O eq 'cygwin' && (!exists $ENV{'CYGWIN'} || $ENV{'CYGWIN'} !~ /server/i);
39    #Modified by Alexandr Ciornii, 2007-03-12
40
41    my $class   = shift;
42    my $session = shift;
43
44    my $nsems = $session->{args}->{NSems} ||
45        $Apache::Session::Lock::Semaphore::nsems;
46
47#    die "You shouldn't set session argument SemaphoreKey to undef"
48#     if exists($session->{args}->{SemaphoreKey}) &&
49#        !defined ($session->{args}->{SemaphoreKey});
50
51    my $sem_key = #exists ($session->{args}->{SemaphoreKey})?
52        $session->{args}->{SemaphoreKey} ||
53        $Apache::Session::Lock::Semaphore::sem_key;
54
55    return bless {read => 0, write => 0, sem => undef, nsems => $nsems,
56        read_sem => undef, sem_key => $sem_key}, $class;
57}
58
59sub acquire_read_lock  {
60    my $self    = shift;
61    my $session = shift;
62
63    return if $self->{read};
64    return if $self->{write};
65
66    if (!$self->{sem}) {
67        $self->{sem} = IPC::Semaphore->new(
68            defined($self->{sem_key})?$self->{sem_key}:IPC_PRIVATE, $self->{nsems},
69            IPC_CREAT | S_IRWXU) || confess("Cannot create semaphore with key $self->{sem_key}; NSEMS: $self->{nsems}: $!");
70    }
71
72    if (!defined $self->{read_sem}) {
73        #The number of semaphores (2^2-2^4, typically) is much less than
74        #the potential number of session ids (2^128, typically), we need
75        #to hash the session id to choose a semaphore.  This hash routine
76        #was stolen from Kernighan's The Practice of Programming.
77
78        my $read_sem = 0;
79        foreach my $el (split(//, $session->{data}->{_session_id})) {
80            $read_sem = 31 * $read_sem + ord($el);
81        }
82        $read_sem %= ($self->{nsems}/2);
83
84        $self->{read_sem} = $read_sem;
85    }
86
87    #The semaphore block is divided into two halves.  The lower half
88    #holds the read semaphores, and the upper half holds the write
89    #semaphores.  Thus we can do atomic upgrade of a read lock to a
90    #write lock.
91
92    $self->{sem}->op($self->{read_sem} + $self->{nsems}/2, 0, SEM_UNDO,
93                     $self->{read_sem},                    1, SEM_UNDO);
94
95    $self->{read} = 1;
96}
97
98sub acquire_write_lock {
99    my $self    = shift;
100    my $session = shift;
101
102    return if($self->{write});
103
104    if (!$self->{sem}) {
105        $self->{sem} = IPC::Semaphore->new(
106            defined($self->{sem_key})?$self->{sem_key}:IPC_PRIVATE, $self->{nsems},
107            IPC_CREAT | S_IRWXU) || confess "Cannot create semaphore with key $self->{sem_key}; NSEMS: $self->{nsems}: $!";
108    }
109
110    if (!defined $self->{read_sem}) {
111        #The number of semaphores (2^2-2^4, typically) is much less than
112        #the potential number of session ids (2^128, typically), we need
113        #to hash the session id to choose a semaphore.  This hash routine
114        #was stolen from Kernighan's The Practice of Programming.
115
116        my $read_sem = 0;
117        foreach my $el (split(//, $session->{data}->{_session_id})) {
118            $read_sem = 31 * $read_sem + ord($el);
119        }
120        $read_sem %= ($self->{nsems}/2);
121
122        $self->{read_sem} = $read_sem;
123    }
124
125    $self->release_read_lock($session) if $self->{read};
126
127    $self->{sem}->op($self->{read_sem},                    0, SEM_UNDO,
128                     $self->{read_sem} + $self->{nsems}/2, 0, SEM_UNDO,
129                     $self->{read_sem} + $self->{nsems}/2, 1, SEM_UNDO);
130
131    $self->{write} = 1;
132}
133
134sub release_read_lock  {
135    my $self    = shift;
136
137    my $session = shift;
138
139    return unless $self->{read};
140
141    $self->{sem}->op($self->{read_sem}, -1, SEM_UNDO);
142
143    $self->{read} = 0;
144}
145
146sub release_write_lock {
147    my $self    = shift;
148    my $session = shift;
149
150    return unless $self->{write};
151
152    $self->{sem}->op($self->{read_sem} + $self->{nsems}/2, -1, SEM_UNDO);
153
154    $self->{write} = 0;
155}
156
157sub release_all_locks  {
158    my $self    = shift;
159    my $session = shift;
160
161    if($self->{read}) {
162        $self->release_read_lock($session);
163    }
164    if($self->{write}) {
165        $self->release_write_lock($session);
166    }
167
168    $self->{read}  = 0;
169    $self->{write} = 0;
170}
171
172sub hash {
173    my $key   = shift;
174    my $nsems = shift;
175    my $hash = 0;
176
177
178}
179
180sub remove {
181    my $self    = shift;
182    if ($self->{sem}) {
183        $self->{sem}->remove();
184    }
185}
186
1871;
188
189
190=pod
191
192=head1 NAME
193
194Apache::Session::Lock::Semaphore - Provides mutual exclusion through semaphores
195
196=head1 SYNOPSIS
197
198 use Apache::Session::Lock::Semaphore;
199
200 my $locker = Apache::Session::Lock::Semaphore->new;
201 die "no semaphores" unless $locker;
202
203 $locker->acquire_read_lock($ref);
204 $locker->acquire_write_lock($ref);
205 $locker->release_read_lock($ref);
206 $locker->release_write_lock($ref);
207 $locker->release_all_locks($ref);
208
209=head1 DESCRIPTION
210
211Apache::Session::Lock::semaphore fulfills the locking interface of
212Apache::Session.  Mutual exclusion is achieved through system semaphores and
213the IPC::Semaphore module.
214
215=head1 CONFIGURATION
216
217The module must know how many semaphores to use, and what semaphore key to
218use. The number of semaphores has an impact on performance.  More semaphores
219means less lock contention. You should use the maximum number of semaphores
220that your platform will allow. On stock NetBSD, OpenBSD, and Solaris systems,
221this is probably 16. On Linux 2.2, this is 32. This module tries to guess
222the number based on your operating system, but it is safer to configure it
223yourself.
224
225To set the number of semaphores, you need to pass an argument in the usual
226Apache::Session style. The name of the argument is NSems, and the value is
227an integer power of 2. For example:
228
229 tie %s, 'Apache::Session::Blah', $id, {NSems => 16};
230
231You may also need to configure the semaphore key that this package uses. By
232default, it uses key 31818.  You can change this using the argument
233SemaphoreKey:
234
235 tie %s, 'Apache::Session::Blah', $id, {NSems => 16, SemaphoreKey => 42};
236
237=head1 PROBLEMS
238
239There are a few problems that people frequently encounter when using this
240package.
241
242If you get an invalid argument message, that usually means that the system
243is unhappy with the number of semaphores that you requested.  Try decreasing
244the number of semaphores.  The semaphore blocks that this package creates
245are persistent until the system is rebooted, so if you request 8 semaphores
246one time and 16 semaphores the next, it won't work.  Use the system
247commands ipcs and ipcrm to inspect and remove unwanted semphore blocks.
248
249=head2 Cygwin
250
251IPC on Cygwin requires running cygserver. Without it, program will exit with
252"Bad System call" message. It cannot be intercepted with eval.
253
254Read /usr/share/doc/Cygwin/cygserver.README for more information.
255
256=head2 Darwin/MacOS X
257
258Darwin and MacOS X may not have semaphores, see
259L<http://sysnet.ucsd.edu/~bellardo/darwin/sysvsem.html>
260
261=head2 *BSD
262
263Error "No space left on device" means that maximum number of semaphores is reached.
264See L<http://www.postgresql.org/docs/7.3/static/kernel-resources.html> for
265more information.
266
267=head1 AUTHOR
268
269This module was written by Jeffrey William Baker <jwbaker@acm.org>.
270
271=head1 SEE ALSO
272
273L<Apache::Session>
274