1#!/usr/bin/ksh93
2
3#
4# CDDL HEADER START
5#
6# The contents of this file are subject to the terms of the
7# Common Development and Distribution License (the "License").
8# You may not use this file except in compliance with the License.
9#
10# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
11# or http://www.opensolaris.org/os/licensing.
12# See the License for the specific language governing permissions
13# and limitations under the License.
14#
15# When distributing Covered Code, include this CDDL HEADER in each
16# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
17# If applicable, add the following below this CDDL HEADER, with the
18# fields enclosed by brackets "[]" replaced with your own identifying
19# information: Portions Copyright [yyyy] [name of copyright owner]
20#
21# CDDL HEADER END
22#
23
24#
25# Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
26#
27
28#
29# filemutexdemo1 - a simple locking demo which supports read/write
30# locks and critical sections (like JAVA's "syncronized" keyword)
31#
32
33# Solaris needs /usr/xpg6/bin:/usr/xpg4/bin because the tools in /usr/bin are not POSIX-conformant
34export PATH=/usr/xpg6/bin:/usr/xpg4/bin:/bin:/usr/bin
35
36# Make sure all math stuff runs in the "C" locale to avoid problems
37# with alternative # radix point representations (e.g. ',' instead of
38# '.' in de_DE.*-locales). This needs to be set _before_ any
39# floating-point constants are defined in this script).
40if [[ "${LC_ALL}" != "" ]] ; then
41    export \
42        LC_MONETARY="${LC_ALL}" \
43        LC_MESSAGES="${LC_ALL}" \
44        LC_COLLATE="${LC_ALL}" \
45        LC_CTYPE="${LC_ALL}"
46        unset LC_ALL
47fi
48export LC_NUMERIC=C
49
50# Definition for a mutex which uses the filesystem for locking
51typeset -T filemutex_t=(
52	typeset name
53	
54	typeset lock_dirname
55	
56	typeset locked_exclusive="false"
57	typeset locked_shared="false"
58	
59	# keep track of subshell level. The problem is that we do not know a
60	# way to figure out whether someone calls "unlock" in a subshell and then
61	# leaves the subshell and calls "unlock" again
62	integer subshell=-1
63	
64	typeset lock_dirname
65	
66	# create a filemutex instance (including lock directory)
67	function create
68	{
69		# make sure we return an error if the init didn't work
70		set -o errexit
71
72		[[ "$1" == "" ]] && return 1
73		
74		_.name="$1"
75		_.lock_dirname="/tmp/filemutex_t_${_.name}.lock"
76		
77		mkdir "${_.lock_dirname}"
78		
79		# last entry, used to mark the mutex as initalised+valid
80		(( _.subshell=.sh.subshell ))
81		return 0
82	}
83
84	# use a filemutex instance (same as "create" but without creating 
85	# the lock directory)
86	function create_child
87	{
88		# make sure we return an error if the init didn't work
89		set -o errexit
90
91		[[ "$1" == "" ]] && return 1
92		
93		_.name="$1"
94		_.lock_dirname="/tmp/filemutex_t_${_.name}.lock"
95		
96		# last entry, used to mark the mutex as initalised+valid
97		(( _.subshell=.sh.subshell ))
98		return 0
99	}
100	
101	function check_subshell
102	{
103		(( _.subshell == .sh.subshell )) && return 0
104		print -u2 -f "filemutex_t.%s(%s): Wrong subshell level\n" "$1" "${_.name}"
105		return 1
106	}
107
108	function try_lock_shared
109	{
110		_.check_subshell "try_lock_shared" || return 1
111		
112		mkdir "${_.lock_dirname}/shared_${PPID}_$$" 2>/dev/null || return 1
113		_.locked_shared="true"
114		return 0
115	}
116	
117	function lock_shared
118	{
119		float interval=0.2
120
121		_.check_subshell "lock_shared" || return 1
122		
123		while ! _.try_lock_shared ; do sleep ${interval} ; (( interval+=interval/10. )) ; done
124		return 0
125	}
126
127	function try_lock_exclusive
128	{
129		_.check_subshell "try_lock_exclusive" || return 1
130		
131		rmdir "${_.lock_dirname}" 2>/dev/null || return 1
132		_.locked_exclusive="true"
133		return 0
134	}
135			
136	function lock_exclusive
137	{
138		float interval=0.2
139		
140		_.check_subshell "lock_exclusive" || return 1
141		
142		while ! _.try_lock_exclusive ; do sleep ${interval} ; (( interval+=interval/10. )) ; done
143		return 0
144	}
145	
146	# critical section support (like java's "synchronized" keyword)
147	function synchronized
148	{
149		integer retcode
150
151		_.check_subshell "synchronized" || return 1
152		
153		_.lock_exclusive
154		
155		"$@"
156		(( retcode=$? ))
157
158		_.unlock
159		
160		return ${retcode}
161	}
162
163	# critical section support with shared lock
164	function synchronized_shared
165	{
166		integer retcode
167
168		_.check_subshell "synchronized_shared" || return 1
169		
170		_.lock_shared
171		
172		"$@"
173		(( retcode=$? ))
174
175		_.unlock
176		
177		return ${retcode}
178	}
179		
180	function unlock
181	{
182		# return an error if rmdir/mkdir/check_subshell fail...
183		set -o errexit
184		
185		_.check_subshell "unlock"
186
187		if ${_.locked_shared} ; then
188			rmdir "${_.lock_dirname}/shared_${PPID}_$$"
189			_.locked_shared="false"
190			return 0
191		elif ${_.locked_exclusive} ; then
192			mkdir "${_.lock_dirname}"
193			_.locked_exclusive="false"
194			return 0
195		fi
196
197		print -u2 -f "filemutex_t.unlock(%s): mutex '%s' not locked." "$1" "${_.name}"
198		return 1
199	}
200	
201	# destroy mutex if noone is using it anymore (not the same as "unset" !!))
202	function destroy
203	{
204		_.check_subshell "destroy" || return 1
205
206		(${_.locked_exclusive} || ${_.locked_shared}) && _.unlock
207		rmdir "${_.lock_dirname}"
208		return 0
209	}
210)
211
212# main
213builtin mkdir
214builtin rmdir
215
216print "## Start."
217
218typeset -r mymutexname="hello_world"
219
220filemutex_t fs
221
222fs.create "${mymutexname}" || print -u2 "Mutex init failed."
223
224print "# Starting child which keeps an exclusive lock for 10 seconds..."
225(
226	filemutex_t child_fs
227	
228	child_fs.create_child "${mymutexname}"
229
230	child_fs.lock_exclusive
231	sleep 10
232	child_fs.unlock
233) &
234
235sleep 1
236
237printf "%T: # Waiting to obtain a shared lock...\n"
238fs.lock_shared
239printf "%T: # Obtained shared lock\n"
240
241printf "fs.locked_exclusive=%s, fs.locked_shared=%s\n" "${fs.locked_exclusive}" "${fs.locked_shared}"
242
243ls -lad /tmp/filemutex*/*
244
245printf "%T: # Executing child which runs printf '|%%s|\\\n' 'hello' 'world' inside a synchronized section\n"
246(
247	filemutex_t child_fs
248	
249	child_fs.create_child "${mymutexname}"
250
251	child_fs.synchronized printf '|%s|\n' 'hello' 'world'
252) &
253
254printf "%T: # Sleeping 5 secs while holding the shared lock...\n"
255sleep 5.
256
257printf "%T: # Releasing shared lock...\n"
258fs.unlock
259
260sleep 5.
261print "# Destroying lock..."
262fs.destroy
263
264print "## Done."
265
266exit 0
267