File: NSDistributedLock.m

package info (click to toggle)
gnustep-base 1.28.1%2Breally1.28.0-5
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 28,008 kB
  • sloc: objc: 223,137; ansic: 35,562; sh: 184; makefile: 128; cpp: 122; xml: 32
file content (346 lines) | stat: -rw-r--r-- 9,255 bytes parent folder | download | duplicates (2)
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
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
/** Implementation for GNU Objective-C version of NSDistributedLock
   Copyright (C) 1997 Free Software Foundation, Inc.

   Written by:  Richard Frith-Macdonald <richard@brainstorm.co.uk>
   Created: November 1997

   This file is part of the GNUstep Base Library.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the Free
   Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110 USA.

   <title>NSDistributedLock class reference</title>
   $Date$ $Revision$
   */

#import "common.h"
#define	EXPOSE_NSDistributedLock_IVARS	1
#import "Foundation/NSDistributedLock.h"
#import "Foundation/NSException.h"
#import "Foundation/NSFileManager.h"
#import "Foundation/NSLock.h"
#import "Foundation/NSValue.h"
#import "GSPrivate.h"


#if	defined(HAVE_SYS_FCNTL_H)
#  include	<sys/fcntl.h>
#elif	defined(HAVE_FCNTL_H)
#  include	<fcntl.h>
#endif


static NSFileManager	*mgr = nil;

/**
 *  This class does not adopt the [(NSLocking)] protocol but supports locking
 *  across processes, including processes on different machines, as long as
 *  they can access a common filesystem.
 */
@implementation NSDistributedLock

+ (void) initialize
{
  if (mgr == nil)
    {
      mgr = RETAIN([NSFileManager defaultManager]);
      [[NSObject leakAt: &mgr] release];
    }
}

/**
 * Return a distributed lock for aPath.
 * See -initWithPath: for details.
 */
+ (NSDistributedLock*) lockWithPath: (NSString*)aPath
{
  return AUTORELEASE([[self alloc] initWithPath: aPath]);
}

/**
 * Forces release of the lock whether the receiver owns it or not.<br />
 * Raises an NSGenericException if unable to remove the lock.
 */
- (void) breakLock
{
  [_localLock lock];
  NS_DURING
    {
      NSDictionary	*attributes;

      DESTROY(_lockTime);
      attributes = [mgr fileAttributesAtPath: _lockPath traverseLink: YES];
      if (attributes != nil)
	{
	  NSDate	*modDate = [attributes fileModificationDate];

	  if ([mgr removeFileAtPath: _lockPath handler: nil] == NO)
	    {
	      NSString	*err = [[NSError _last] localizedDescription];

	      attributes = [mgr fileAttributesAtPath: _lockPath
					traverseLink: YES];
	      if ([modDate isEqual: [attributes fileModificationDate]] == YES)
		{
		  [NSException raise: NSGenericException
		    format: @"Failed to remove lock directory '%@' - %@",
		    _lockPath, err];
		}
	    }
	}
    }
  NS_HANDLER
    {
      [_localLock unlock];
      [localException raise];
    }
  NS_ENDHANDLER
  [_localLock unlock];
}

- (void) dealloc
{
  if (_lockTime != nil)
    {
      NSLog(@"[%@-dealloc] still locked for %@ since %@",
        NSStringFromClass([self class]), _lockPath, _lockTime);
      [self unlock];
    }
  RELEASE(_lockPath);
  RELEASE(_lockTime);
  RELEASE(_localLock);
  [super dealloc];
}

- (NSString*) description
{
  NSString	*result;

  [_localLock lock];
  if (_lockTime == nil)
    {
      result = [[super description] stringByAppendingFormat:
        @" path '%@' not locked", _lockPath];
    }
  else
    {
      result = [[super description] stringByAppendingFormat:
        @" path '%@' locked at %@", _lockPath, _lockTime];
    }
  [_localLock unlock];
  return result;
}

/**
 * Initialises the receiver with the specified filesystem path.<br />
 * The location in the filesystem must be accessible for this
 * to be usable.  That is, the processes using the lock must be able
 * to access, create, and destroy files at the path.<br />
 * The directory in which the last path component resides must already
 * exist ... create it using NSFileManager if you need to.
 */
- (id) initWithPath: (NSString*)aPath
{
  NSString	*lockDir;
  BOOL		isDirectory;

  _localLock = [NSLock new];
  _lockPath = [[aPath stringByStandardizingPath] copy];
  _lockTime = nil;

  lockDir = [_lockPath stringByDeletingLastPathComponent];
  if ([mgr fileExistsAtPath: lockDir isDirectory: &isDirectory] == NO)
    {
      NSLog(@"part of the path to the lock file '%@' is missing\n", aPath);
      DESTROY(self);
      return nil;
    }
  if (isDirectory == NO)
    {
      NSLog(@"part of the path to the lock file '%@' is not a directory\n",
	_lockPath);
      DESTROY(self);
      return nil;
    }
  if ([mgr isWritableFileAtPath: lockDir] == NO)
    {
      NSLog(@"parent directory of lock file '%@' is not writable\n", _lockPath);
      DESTROY(self);
      return nil;
    }
  if ([mgr isExecutableFileAtPath: lockDir] == NO)
    {
      NSLog(@"parent directory of lock file '%@' is not accessible\n",
		_lockPath);
      DESTROY(self);
      return nil;
    }
  return self;
}

/**
 * Returns the date at which the lock was acquired by <em>any</em>
 * NSDistributedLock using the same path.  If nothing has
 * the lock, this returns nil.
 */
- (NSDate*) lockDate
{
  NSDictionary	*attributes;

  attributes = [mgr fileAttributesAtPath: _lockPath traverseLink: YES];
  return [attributes fileModificationDate];
}

/**
 * Attempt to acquire the lock and return YES on success, NO on failure.<br />
 * May raise an NSGenericException if a problem occurs.
 */
- (BOOL) tryLock
{
  BOOL		locked = NO;

  [_localLock lock];
  NS_DURING
    {
      NSMutableDictionary	*attributesToSet;
      NSDictionary		*attributes;

      if (nil != _lockTime)
	{
	  [NSException raise: NSGenericException
		      format: @"Attempt to re-lock distributed lock %@",
	    _lockPath];
        }
      attributesToSet = [NSMutableDictionary dictionaryWithCapacity: 1];
      [attributesToSet setObject: [NSNumber numberWithUnsignedInt: 0755]
			  forKey: NSFilePosixPermissions];

      /* Here we depend on the fact that directory creation will fail if
       * the directory already exists.
       * We don't worry about any intermediate directories since we checked
       * those when the receiver was initialised in the -initWithPath: method.
       */
      locked = [mgr createDirectoryAtPath: _lockPath
			       attributes: attributesToSet];
      if (NO == locked)
	{
	  BOOL	dir;

	  /* We expect the directory creation to have failed because
	   * it already exists as another processes lock.
	   * If the directory doesn't exist, then either the other
	   * process has removed it's lock (and we can retry)
	   * or we have a severe problem!
	   */
	  if ([mgr fileExistsAtPath: _lockPath isDirectory: &dir] == NO)
	    {
	      locked = [mgr createDirectoryAtPath: _lockPath
		      withIntermediateDirectories: YES
				       attributes: attributesToSet
					    error: NULL];
	      if (NO == locked)
		{
		  NSLog(@"Failed to create lock directory '%@' - %@",
		    _lockPath, [NSError _last]);
		}
	    }
	}

      if (YES == locked)
	{
	  attributes = [mgr fileAttributesAtPath: _lockPath
				    traverseLink: YES];
	  if (attributes == nil)
	    {
	      [NSException raise: NSGenericException
		format: @"Unable to get attributes of lock file we made at %@",
		_lockPath];
	    }
	  ASSIGN(_lockTime, [attributes fileModificationDate]);
	  if (nil == _lockTime)
	    {
	      [NSException raise: NSGenericException
		format: @"Unable to get date of lock file we made at %@",
		_lockPath];
	    }
	}
    }
  NS_HANDLER
    {
      [_localLock unlock];
      [localException raise];
    }
  NS_ENDHANDLER
  [_localLock unlock];
  return locked;
}

/**
 * Releases the lock.  Raises an NSGenericException if unable to release
 * the lock (for instance if the receiver does not own it or another
 * process has broken it).
 */
- (void) unlock
{
  [_localLock lock];
  NS_DURING
    {
      NSDictionary	*attributes;

      if (_lockTime == nil)
	{
	  [NSException raise: NSGenericException format: @"not locked by us"];
	}

      /* Don't remove the lock if it has already been broken by someone
       * else and re-created.  Unfortunately, there is a window between
       * testing and removing, but we do the bset we can.
       */
      attributes = [mgr fileAttributesAtPath: _lockPath traverseLink: YES];
      if (attributes == nil)
	{
	  DESTROY(_lockTime);
	  [NSException raise: NSGenericException
		      format: @"lock '%@' already broken", _lockPath];
	}
      if ([_lockTime isEqual: [attributes fileModificationDate]])
	{
	  DESTROY(_lockTime);
	  if ([mgr removeFileAtPath: _lockPath handler: nil] == NO)
	    {
	      [NSException raise: NSGenericException
			  format: @"Failed to remove lock directory '%@' - %@",
			      _lockPath, [NSError _last]];
	    }
	}
      else
	{
	  DESTROY(_lockTime);
	  [NSException raise: NSGenericException
		      format: @"lock '%@' already broken and in use again",
	    _lockPath];
	}
      DESTROY(_lockTime);
    }
  NS_HANDLER
    {
      [_localLock unlock];
      [localException raise];
    }
  NS_ENDHANDLER
  [_localLock unlock];
}

@end