[m-rev.] for review: make the deep profiler work again
Zoltan Somogyi
zs at cs.mu.OZ.AU
Thu Nov 14 10:35:09 AEDT 2002
On 14-Nov-2002, Zoltan Somogyi <zs at cs.mu.OZ.AU> wrote:
> Make the deep profiler work again.
The diffs of mdprof_cgi.m and timeout.m may be hard to read, since
mdprof_cgi.m was totally rewritten and timeout.m was partially rewritten.
I therefore enclose their new contents.
Zoltan.
::::::::::::::
mdprof_cgi.m
::::::::::::::
%-----------------------------------------------------------------------------%
% Copyright (C) 2001-2002 The University of Melbourne.
% This file may only be copied under the terms of the GNU General
% Public License - see the file COPYING in the Mercury distribution.
%-----------------------------------------------------------------------------%
%
% Author of initial version: conway.
% Author of this version: zs.
%
% This file contains the CGI "script" that is executed by the web server
% to handle web page requests implemented by the Mercury deep profiler server.
:- module mdprof_cgi.
:- interface.
:- import_module io.
:- pred main(io__state::di, io__state::uo) is cc_multi.
:- implementation.
:- import_module profile, interface, startup, query, conf, timeout, util.
:- import_module bool, char, string, int, array, list, set.
:- import_module require, std_util, getopt.
:- import_module int, string, list, array, map, exception, require.
% The web server should always set QUERY_STRING. It may also pass its contents
% as arguments, but if any characters specials to the shell occur in the query,
% they will screw up the argument list. We therefore look at the argument list
% only if QUERY_STRING isn't set, which means that the program was invoked
% from the command line for debugging.
main -->
write_html_header,
io__get_environment_var("QUERY_STRING", MaybeQueryString),
(
{ MaybeQueryString = yes(QueryString0) },
{ getopt__process_options(option_ops(short, long, defaults),
[], _, MaybeOptions) },
{
MaybeOptions = ok(Options)
;
MaybeOptions = error(_Msg),
error("mdprof_cgi: error parsing empty command line")
},
{ split(QueryString0, query_separator_char, Pieces) },
( { Pieces = [CmdStr, PrefStr, FileName] } ->
{ Cmd = url_component_to_cmd(CmdStr, menu) },
process_query(Cmd, yes(PrefStr), FileName,
Options)
; { Pieces = [CmdStr, FileName] } ->
{ Cmd = url_component_to_cmd(CmdStr, menu) },
process_query(Cmd, no, FileName, Options)
; { Pieces = [FileName] } ->
process_query(menu, no, FileName, Options)
;
io__set_exit_status(1),
% Give the simplest URL in the error message.
io__write_string("Bad URL; expected filename\n")
)
;
{ MaybeQueryString = no },
process_command_line
).
:- pred process_command_line(io__state::di, io__state::uo) is cc_multi.
process_command_line -->
io__progname_base("mdprof_cgi", ProgName),
io__command_line_arguments(Args0),
% io__write_string("Args0: "),
% io__write_list(Args0, " ", write_bracketed_string),
% io__nl,
{ getopt__process_options(option_ops(short, long, defaults),
Args0, Args, MaybeOptions) },
(
{ MaybeOptions = ok(Options) },
process_args(Args, Options)
;
{ MaybeOptions = error(Msg) },
io__set_exit_status(1),
io__format("%s: error parsing options: %s\n",
[s(ProgName), s(Msg)])
).
:- pred process_args(list(string)::in, option_table::in,
io__state::di, io__state::uo) is cc_multi.
process_args(Args, Options) -->
( { Args = [FileName] } ->
% Although this mode of usage is not intended for production
% use, allowing the filename and a limited range of commands
% to be supplied on the command line makes debugging very much
% easier.
process_query(default_cmd(Options), no, FileName, Options)
;
io__set_exit_status(1),
% The options are deliberately not documented; they change
% quite rapidly, based on the debugging needs of the moment.
io__write_string("Usage: mdprof_cgi [filename]\n"),
io__write_list(Args, " ", write_bracketed_string)
).
:- pred write_bracketed_string(string::in, io__state::di, io__state::uo)
is det.
write_bracketed_string(S) -->
io__write_string("<"),
io__write_string(S),
io__write_string(">").
:- pred write_html_header(io__state::di, io__state::uo) is det.
write_html_header -->
io__write_string(html_header_text),
io__flush_output.
:- func html_header_text = string.
html_header_text = "Content-type: text/html\n\n".
%-----------------------------------------------------------------------------%
:- pred process_query(cmd::in, maybe(string)::in, string::in,
option_table::in, io__state::di, io__state::uo) is cc_multi.
process_query(Cmd, MaybePrefStr, DataFileName, Options) -->
{
MaybePrefStr = yes(PrefStr),
MaybePref = url_component_to_maybe_pref(PrefStr)
;
MaybePrefStr = no,
MaybePref = no
},
{
MaybePref = yes(Pref)
;
MaybePref = no,
Pref = default_preferences
},
{ ToServerPipe = to_server_pipe_name(DataFileName) },
{ FromServerPipe = from_server_pipe_name(DataFileName) },
{ StartupFile = server_startup_name(DataFileName) },
{ MutexFile = mutex_file_name(DataFileName) },
{ lookup_bool_option(Options, debug, Debug) },
{ WantFile = want_file_name },
make_want_file(WantFile),
get_lock(Debug, MutexFile),
(
{ Debug = yes }
% Do not set up any cleanups; leave all files around,
% since they may be needed for postmortem examination.
;
{ Debug = no },
setup_signals(MutexFile, want_dir, want_prefix)
),
check_for_existing_fifos(ToServerPipe, FromServerPipe, FifoCount),
( { FifoCount = 0 } ->
handle_query_from_new_server(Cmd, Pref, DataFileName,
ToServerPipe, FromServerPipe, StartupFile,
MutexFile, WantFile, Options)
; { FifoCount = 2 } ->
handle_query_from_existing_server(Cmd, Pref,
ToServerPipe, FromServerPipe,
MutexFile, WantFile, Options)
;
release_lock(Debug, MutexFile),
remove_want_file(WantFile),
io__set_exit_status(1),
io__write_string("mdprof internal error: bad fifo count")
).
% Handle the given query using the existing server. Delete the mutex and want
% files when we get out of the critical region.
:- pred handle_query_from_existing_server(cmd::in, preferences::in,
string::in, string::in, string::in, string::in, option_table::in,
io__state::di, io__state::uo) is det.
handle_query_from_existing_server(Cmd, Pref, ToServerPipe, FromServerPipe,
MutexFile, WantFile, Options) -->
{ lookup_bool_option(Options, debug, Debug) },
send_term(ToServerPipe, Debug, cmd_pref(Cmd, Pref)),
release_lock(Debug, MutexFile),
remove_want_file(WantFile),
recv_string(FromServerPipe, Debug, ResponseFileName),
{ CatCmd = string__format("cat %s", [s(ResponseFileName)]) },
io__call_system(CatCmd, _),
(
{ Debug = yes }
% Leave the response file to be examined.
;
{ Debug = no },
{ RmCmd = string__format("rm %s", [s(ResponseFileName)]) },
io__call_system(RmCmd, _)
).
% Handle the given query and then become the new server. Delete the mutex
% and want files when we get out of the critical region.
:- pred handle_query_from_new_server(cmd::in, preferences::in, string::in,
string::in, string::in, string::in, string::in, string::in,
option_table::in, io__state::di, io__state::uo) is cc_multi.
handle_query_from_new_server(Cmd, Pref, FileName, ToServerPipe, FromServerPipe,
StartupFile, MutexFile, WantFile, Options) -->
server_name(Machine),
{ lookup_bool_option(Options, canonical_clique, Canonical) },
{ lookup_bool_option(Options, server_process, ServerProcess) },
{ lookup_bool_option(Options, debug, Debug) },
{ lookup_bool_option(Options, record_startup, RecordStartup) },
(
{ RecordStartup = yes },
io__open_output(StartupFile, StartupStreamRes),
(
{ StartupStreamRes = ok(StartupStream0) },
{ MaybeStartupStream = yes(StartupStream0) },
register_file_for_cleanup(StartupFile)
;
{ StartupStreamRes = error(_) },
{ error("cannot create startup file") }
)
;
{ RecordStartup = no },
{ MaybeStartupStream = no }
),
read_and_startup(Machine, [FileName], Canonical, MaybeStartupStream,
Res),
(
{ Res = ok(Deep) },
try_exec(Cmd, Pref, Deep, HTML),
(
{ MaybeStartupStream = yes(StartupStream1) },
io__format(StartupStream1, "query 0 output:\n%s\n",
[s(HTML)]),
% If we don't flush the output before the fork, it will
% be flushed twice, once by the parent process and
% once by the child process.
io__flush_output(StartupStream1)
;
{ MaybeStartupStream = no }
),
(
{ ServerProcess = no },
% --no-server process should be specified only during
% debugging.
release_lock(Debug, MutexFile),
remove_want_file(WantFile),
io__write_string(HTML)
;
{ ServerProcess = yes },
make_pipes(FileName, Success),
(
{ Success = yes },
io__write_string(HTML),
io__flush_output,
start_server(Options,
ToServerPipe, FromServerPipe,
MaybeStartupStream,
MutexFile, WantFile, Deep)
;
{ Success = no },
release_lock(Debug, MutexFile),
remove_want_file(WantFile),
io__set_exit_status(1),
io__write_string("could not make pipes\n")
)
)
;
{ Res = error(Error) },
release_lock(Debug, MutexFile),
remove_want_file(WantFile),
io__set_exit_status(1),
io__format("error reading data file: %s\n", [s(Error)])
).
% Become the new server. Delete the mutex and want files when we get out
% of the critical region.
:- pred start_server(option_table::in, string::in, string::in,
maybe(io__output_stream)::in, string::in, string::in, deep::in,
io__state::di, io__state::uo) is cc_multi.
start_server(Options, ToServerPipe, FromServerPipe, MaybeStartupStream,
MutexFile, WantFile, Deep) -->
{ lookup_bool_option(Options, detach_process, DetachProcess) },
{ lookup_bool_option(Options, record_loop, RecordLoop) },
{ lookup_bool_option(Options, debug, Debug) },
(
{ DetachProcess = no },
% We behave as if we were in the child, to allow the server
% loop to be debugged.
{ DetachRes = in_child(child_has_no_parent) }
;
{ DetachProcess = yes },
detach_process(DetachRes)
),
(
{ DetachRes = in_child(ChildHasParent) } ->
% We are in the child; start serving queries.
(
{ ChildHasParent = child_has_parent },
% Our parent process will perform the file removals
% needed to exit the critical section; we don't
% want to duplicate them. We also don't want to delete
% the pipes we need or the startup file.
unregister_file_for_cleanup(MutexFile),
unregister_file_for_cleanup(WantFile)
;
{ ChildHasParent = child_has_no_parent },
% We don't actually have a parent process, so we need
% to perform the file removals needed to exit the
% critical section ourselves.
release_lock(Debug, MutexFile),
remove_want_file(WantFile)
),
(
{ RecordLoop = yes },
{ MaybeDebugStream = MaybeStartupStream }
;
{ RecordLoop = no },
{ MaybeDebugStream = no }
),
{ lookup_int_option(Options, timeout, TimeOut) },
{ lookup_bool_option(Options, canonical_clique, Canonical) },
server_loop(ToServerPipe, FromServerPipe, TimeOut,
MaybeDebugStream, Debug, Canonical, 0, Deep)
;
{ DetachRes = in_parent } ->
% We are in the parent after we spawned the child. We cause
% the process to exit simply by not calling server_loop.
%
% We leave the pipes and the startup file; we clean up only
% the files involved in the critical section.
release_lock(Debug, MutexFile),
remove_want_file(WantFile)
;
% We are in the parent because the fork failed. Again we cause
% the process to exit simply by not calling server_loop, but we
% also report the failure through the exit status. We don't
% report it via the generated web page, since the cause could
% be transitory and may not recur.
%
% This deletes all the files created by the process, including
% WantFile and MutexFile, with MutexFile being deleted last.
delete_cleanup_files,
io__set_exit_status(1)
).
:- pred server_loop(string::in, string::in, int::in,
maybe(io__output_stream)::in, bool::in, bool::in, int::in, deep::in,
io__state::di, io__state::uo) is cc_multi.
server_loop(ToServerPipe, FromServerPipe, TimeOut0, MaybeStartupStream,
Debug, Canonical, QueryNum0, Deep0) -->
setup_timeout(TimeOut0),
{ QueryNum = QueryNum0 + 1 },
recv_term(ToServerPipe, Debug, CmdPref0),
(
{ MaybeStartupStream = yes(StartupStream0) },
io__format(StartupStream0, "server loop query %d\n",
[i(QueryNum)]),
io__write(StartupStream0, CmdPref0),
io__nl(StartupStream0),
io__flush_output(StartupStream0)
;
{ MaybeStartupStream = no }
),
{ CmdPref0 = cmd_pref(Cmd0, Pref0) },
( { Cmd0 = restart } ->
read_and_startup(Deep0 ^ server_name, [Deep0 ^ data_file_name],
Canonical, MaybeStartupStream, MaybeDeep),
(
{ MaybeDeep = ok(Deep) },
{ MaybeMsg = no },
{ Cmd = menu }
;
{ MaybeDeep = error(ErrorMsg) },
{ MaybeMsg = yes(ErrorMsg) },
{ Deep = Deep0 },
{ Cmd = quit }
)
;
{ Deep = Deep0 },
{ MaybeMsg = no },
{ Cmd = Cmd0 }
),
(
{ MaybeMsg = yes(HTML) }
;
{ MaybeMsg = no },
try_exec(Cmd, Pref0, Deep, HTML)
),
{ ResponseFileName =
response_file_name(Deep0 ^ data_file_name, QueryNum) },
io__open_output(ResponseFileName, ResponseRes),
(
{ ResponseRes = ok(ResponseStream) }
;
{ ResponseRes = error(_) },
{ error("cannot open response file") }
),
io__write_string(ResponseStream, HTML),
io__close_output(ResponseStream),
send_string(FromServerPipe, Debug, ResponseFileName),
(
{ MaybeStartupStream = yes(StartupStream1) },
io__format(StartupStream1, "query %d output:\n%s\n",
[i(QueryNum), s(HTML)]),
io__flush_output(StartupStream1)
;
{ MaybeStartupStream = no }
),
( { Cmd = quit } ->
% The lack of a recursive call here shuts down the server.
%
% This deletes all the files created by the process, including
% WantFile and MutexFile, with MutexFile being deleted last.
delete_cleanup_files
; { Cmd = timeout(TimeOut) } ->
server_loop(ToServerPipe, FromServerPipe, TimeOut,
MaybeStartupStream, Debug, Canonical, QueryNum, Deep)
;
server_loop(ToServerPipe, FromServerPipe, TimeOut0,
MaybeStartupStream, Debug, Canonical, QueryNum, Deep)
).
%-----------------------------------------------------------------------------%
:- pred make_pipes(string::in, bool::out, io__state::di, io__state::uo) is det.
make_pipes(FileName, Success) -->
{ ToServerPipe = to_server_pipe_name(FileName) },
{ FromServerPipe = from_server_pipe_name(FileName) },
{ MakeToServerPipeCmd = make_pipe_cmd(ToServerPipe) },
{ MakeFromServerPipeCmd = make_pipe_cmd(FromServerPipe) },
io__call_system(MakeToServerPipeCmd, ToServerRes),
io__call_system(MakeFromServerPipeCmd, FromServerRes),
(
{ ToServerRes = ok(0) },
{ FromServerRes = ok(0) }
->
register_file_for_cleanup(ToServerPipe),
register_file_for_cleanup(FromServerPipe),
{ Success = yes }
;
% In case one of the pipes was created.
io__remove_file(ToServerPipe, _),
io__remove_file(FromServerPipe, _),
{ Success = no }
).
%-----------------------------------------------------------------------------%
:- pragma foreign_decl("C", "
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
").
:- pragma foreign_decl("C", "
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
").
:- pred check_for_existing_fifos(string::in, string::in, int::out,
io__state::di, io__state::uo) is det.
:- pragma foreign_proc("C",
check_for_existing_fifos(Fifo1::in, Fifo2::in, FifoCount::out,
S0::di, S::uo),
[will_not_call_mercury, promise_pure, tabled_for_io],
"
struct stat statbuf;
int status;
FifoCount = 0;
status = stat(Fifo1, &statbuf);
if ((status == 0) && (S_ISFIFO(statbuf.st_mode))) {
FifoCount++;
}
status = stat(Fifo2, &statbuf);
if ((status == 0) && (S_ISFIFO(statbuf.st_mode))) {
FifoCount++;
}
S = S0;
").
:- type child_has_parent
---> child_has_parent
; child_has_no_parent.
:- type detach_process_result
---> in_child(child_has_parent)
; in_parent
; fork_failed.
:- pred detach_process(detach_process_result::out,
io__state::di, io__state::uo) is cc_multi.
detach_process(Result) -->
raw_detach_process(ResCode),
{ ResCode < 0 ->
Result = fork_failed
; ResCode > 0 ->
Result = in_parent
;
Result = in_child(child_has_parent)
}.
% Raw_detach_process performs a fork.
%
% If the fork succeeds, the result returned by detach_process is:
%
% - a positive number in the parent, and
% - zero in the child.
%
% If the fork fails, the result returned by detach_process is:
%
% - a negative number in the parent (there is no child process).
:- pred raw_detach_process(int::out, io__state::di, io__state::uo) is cc_multi.
:- pragma foreign_proc("C",
raw_detach_process(ResCode::out, S0::di, S::uo),
[will_not_call_mercury, promise_pure],
"{
pid_t status;
fflush(stdout);
fflush(stderr);
status = fork();
if (status < 0) {
ResCode = -1;
} else if (status > 0) {
ResCode = 1;
} else {
#ifdef MR_HAVE_SETPGID
/* detach the server process from the parent's process group */
setpgid(0, 0);
#else
/* hope that web server doesn't depend on the process group */
#endif
ResCode = 0;
}
S = S0;
}").
%-----------------------------------------------------------------------------%
:- type option
---> canonical_clique
; clique
; debug
; detach_process
; modules
; proc
; quit
; root
; record_startup
; record_loop
; server_process
; timeout
; write_query_string.
:- type options ---> options.
:- type option_table == (option_table(option)).
:- pred short(char::in, option::out) is semidet.
short('c', canonical_clique).
short('C', clique).
short('d', debug).
short('m', modules).
short('p', proc).
short('q', quit).
short('r', root).
short('s', server_process).
short('t', timeout).
short('w', write_query_string).
:- pred long(string::in, option::out) is semidet.
long("canonical-clique", canonical_clique).
long("clique", clique).
long("debug", debug).
long("detach-process", detach_process).
long("modules", modules).
long("proc", proc).
long("quit", quit).
long("root", root).
long("record-startup", record_startup).
long("record-loop", record_loop).
long("server-process", server_process).
long("timeout", timeout).
long("write-query-string", write_query_string).
:- pred defaults(option::out, option_data::out) is nondet.
defaults(Option, Data) :-
semidet_succeed,
defaults0(Option, Data).
:- pred defaults0(option::out, option_data::out) is multi.
defaults0(canonical_clique, bool(no)).
defaults0(clique, int(0)).
defaults0(debug, bool(no)).
defaults0(detach_process, bool(yes)).
defaults0(modules, bool(no)).
defaults0(proc, int(0)).
defaults0(quit, bool(no)).
defaults0(root, bool(no)).
defaults0(record_loop, bool(yes)).
defaults0(record_startup, bool(yes)).
defaults0(server_process, bool(yes)).
defaults0(timeout, int(30)).
defaults0(write_query_string, bool(yes)).
:- func default_cmd(option_table) = cmd.
default_cmd(Options) = Cmd :-
lookup_bool_option(Options, quit, Quit),
lookup_bool_option(Options, root, Root),
lookup_bool_option(Options, modules, Modules),
lookup_int_option(Options, clique, CliqueNum),
lookup_int_option(Options, proc, ProcNum),
( Root = yes ->
Cmd = root(no)
; Modules = yes ->
Cmd = modules
; CliqueNum > 0 ->
Cmd = clique(CliqueNum)
; ProcNum > 0 ->
Cmd = proc(ProcNum)
; Quit = yes ->
Cmd = quit
;
Cmd = menu
).
%-----------------------------------------------------------------------------%
::::::::::::::
timeout.m
::::::::::::::
%-----------------------------------------------------------------------------%
% Copyright (C) 2001-2002 The University of Melbourne.
% This file may only be copied under the terms of the GNU General
% Public License - see the file COPYING in the Mercury distribution.
%-----------------------------------------------------------------------------%
%
% Author: zs.
%
% This module implements timeouts and cleanup for the deep profiler.
%
% The timeout design we use and its rationale are given in the file DESIGN.
%
% The cleanup system consists of an array of filenames. When the profiler
% creates a temporary file, it adds its name to the array; when it deletes
% the temporary file, it deletes its name from the array. When we get an
% unexpected signal, we clean up by deleting all the temporary files named
% in the array. The
%
% We establish the exit action to clean up the files as soon as they are
% created, but we don't want the parent process after the fork to delete them
% while they are still in use by the child process. This is prevented by the
% boolean flag process_is_detached_server.
:- module timeout.
:- interface.
:- import_module bool, io.
% Add the given file name to the list of files to be cleaned up.
:- pred register_file_for_cleanup(string::in, io__state::di, io__state::uo)
is det.
% Remove the given file name from the list of files to be cleaned up.
:- pred unregister_file_for_cleanup(string::in, io__state::di, io__state::uo)
is det.
% Remove all file names from the list of files to be cleaned up.
:- pred unregister_all_files_for_cleanup(io__state::di, io__state::uo) is det.
% Delete all the files on the cleanup list.
:- pred delete_cleanup_files(io__state::di, io__state::uo) is det.
% Set up signal handlers for all the signals we can catch. The three strings
% specify the name of the mutex file, the name of the directory containing the
% `want' files, and the prefix of the names of the `want' files.
:- pred setup_signals(string::in, string::in, string::in,
io__state::di, io__state::uo) is det.
% Set up a timeout for the given number of minutes in the future.
:- pred setup_timeout(int::in, io__state::di, io__state::uo) is det.
% Get the lock on the named mutex file if the bool is `no'.
% If the bool is `yes', meaning debugging is enabled, do nothing.
:- pred get_lock(bool::in, string::in,
io__state::di, io__state::uo) is det.
% Release the lock on the named mutex file if the bool is `no'.
% If the bool is `yes', meaning debugging is enabled, do nothing.
:- pred release_lock(bool::in, string::in,
io__state::di, io__state::uo) is det.
% Create the `want' file with the given name.
:- pred make_want_file(string::in, io__state::di, io__state::uo) is det.
% Delete the `want' file with the given name.
:- pred remove_want_file(string::in, io__state::di, io__state::uo) is det.
:- implementation.
:- import_module int.
:- import_module string.
:- pragma foreign_decl("C",
"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <signal.h> /* for signal numbers */
#include <unistd.h> /* for alarm() */
#include <stdlib.h> /* for atexit() */
#include <errno.h> /* for EEXIST etc */
#include ""mercury_signal.h""
#define MP_MAX_CLEANUP_FILES 20 /* this should be plenty */
extern const char *MP_cleanup_files[MP_MAX_CLEANUP_FILES];
extern int MP_cleanup_file_next;
extern void MP_maybe_print_cleanup_files(const char *msg);
extern void MP_register_cleanup_file(const char *filename);
extern void MP_unregister_cleanup_file(const char *filename);
extern void MP_handle_fatal_exception(void *data);
extern void MP_delete_cleanup_files(void);
extern void MP_delete_cleanup_files_and_exit_failure(
const char *signal_name);
extern int MP_timeout_seconds;
extern const char *MP_timeout_mutex_file;
extern const char *MP_timeout_want_dir;
extern const char *MP_timeout_want_prefix;
typedef struct
{
int MP_signum;
void (*MP_handler)(void);
} MP_sig_handler;
extern const MP_sig_handler MP_signal_structs[];
extern void MP_handle_timeout(void);
extern void MP_handle_sig_term(void);
extern void MP_handle_sig_hup(void);
extern void MP_handle_sig_int(void);
extern void MP_handle_sig_quit(void);
extern void MP_handle_sig_ill(void);
extern void MP_handle_sig_abrt(void);
extern void MP_handle_sig_bus(void);
extern void MP_handle_sig_fpe(void);
extern void MP_handle_sig_segv(void);
extern void MP_handle_sig_pipe(void);
extern MR_bool MP_do_try_get_lock(const char *mutex_file);
extern void MP_do_get_lock(const char *mutex_file);
extern void MP_do_release_lock(const char *mutex_file);
").
:- pragma foreign_code("C",
"
#if defined(MR_HAVE_DIRENT_H)
#include <sys/types.h>
#include <dirent.h>
#else
/* if we get here, the deep profiler isn't enabled */
#endif
const char *MP_cleanup_files[MP_MAX_CLEANUP_FILES];
int MP_cleanup_file_next = 0;
int MP_timeout_seconds = 30 * 60;
const char *MP_timeout_mutex_file = NULL;
const char *MP_timeout_want_dir = NULL;
const char *MP_timeout_want_prefix = NULL;
/* set this variable to MR_TRUE to debug the code cleanup array */
MR_bool MP_print_cleanup_files = MR_FALSE;
void
MP_maybe_print_cleanup_files(const char *msg)
{
int i;
if (MP_print_cleanup_files) {
fprintf(stderr, ""\n%s cleanup files:\n"", msg);
for (i = 0; i < MP_cleanup_file_next; i++) {
fprintf(stderr, ""%i %s\n"", i, MP_cleanup_files[i]);
}
}
}
void
MP_register_cleanup_file(const char *filename)
{
int i;
if (MP_cleanup_file_next >= MP_MAX_CLEANUP_FILES - 1) {
MR_fatal_error(""MP_register_cleanup_file: too many entries"");
}
for (i = 0; i < MP_cleanup_file_next; i++) {
if (MR_streq(filename, MP_cleanup_files[i])) {
MR_fatal_error(""MP_register_cleanup_file: duplicate"");
}
}
MP_cleanup_files[MP_cleanup_file_next] = filename;
MP_cleanup_file_next++;
MP_maybe_print_cleanup_files(""register"");
}
void
MP_unregister_cleanup_file(const char *filename)
{
int i;
int j;
for (i = 0; i < MP_cleanup_file_next; i++) {
if (MR_streq(filename, MP_cleanup_files[i])) {
/* shift the array entries above index i down one */
for (j = i + 1; j < MP_cleanup_file_next; j++) {
MP_cleanup_files[j - 1] = MP_cleanup_files[j];
}
MP_cleanup_file_next--;
MP_maybe_print_cleanup_files(""unregister"");
return;
}
}
MR_fatal_error(""MP_unregister_cleanup_file: not found"");
}
void
MP_handle_fatal_exception(void *data)
{
/* we ignore data */
MP_delete_cleanup_files();
}
void
MP_delete_cleanup_files(void)
{
int i;
MR_bool delayed_mutex_file;
/*
** We want to remove the mutex file only after we have removed the
** files manipulated by the critical section it was protecting.
*/
MP_maybe_print_cleanup_files(""delete"");
delayed_mutex_file = MR_FALSE;
for (i = 0; i < MP_cleanup_file_next; i++) {
if (MR_streq(MP_timeout_mutex_file, MP_cleanup_files[i])) {
delayed_mutex_file = MR_TRUE;
} else {
if (remove(MP_cleanup_files[i]) != 0) {
perror(MP_cleanup_files[i]);
}
}
}
if (delayed_mutex_file) {
if (remove(MP_timeout_mutex_file) != 0) {
perror(MP_timeout_mutex_file);
}
}
MP_cleanup_file_next = 0;
}
void
MP_delete_cleanup_files_and_exit_failure(const char *signal_name)
{
FILE *fp;
char buf[1024]; /* that should be big enough */
#ifdef MP_DEBUG_SIGNAL
fp = fopen(""/tmp/mdprof_signal"", ""w"");
if (fp != NULL) {
fprintf(fp, ""%s\n"", signal_name);
(void) fclose(fp);
}
#endif
MP_delete_cleanup_files();
#ifdef MP_DEBUG_SIGNAL
sprintf(buf, ""Mercury deep profiler: received unexpected signal %s"",
signal_name);
MR_fatal_error(buf);
#else
exit(EXIT_FAILURE);
#endif
}
/*
** SIGALRM alarm signal indicates a timeout. SIGTERM usually indicates the
** machine is being shut down. The others are there to catch forceful shutdowns
** during development, both intentional ones where the programmer sends the
** signal and those caused by bugs in the server code. We would like to include
** all catchable, fatal signals in this list, but that set is somewhat OS
** dependent. The set whose existence we test for here includes all the
** signals that are at all likely to be sent to server process.
**
** We don't test for the existence of SIGALRM, because we want compilation to
** fail if it does not exist. Without alarm signals, server processes will
** never be timed out, and thus constitute a resource leak (mostly of virtual
** memory/swap space).
**
** We could avoid this problem if we had a version of atexit that executed
** its actions even when the program exits after a signal.
*/
const MP_sig_handler MP_signal_structs[] =
{
{ SIGALRM, MP_handle_timeout },
#ifdef SIGTERM
{ SIGTERM, MP_handle_sig_term },
#endif
#ifdef SIGHUP
{ SIGHUP, MP_handle_sig_hup },
#endif
#ifdef SIGINT
{ SIGINT, MP_handle_sig_int },
#endif
#ifdef SIGQUIT
{ SIGQUIT, MP_handle_sig_quit },
#endif
#ifdef SIGILL
{ SIGILL, MP_handle_sig_ill },
#endif
#ifdef SIGABRT
{ SIGABRT, MP_handle_sig_abrt },
#endif
#ifdef SIGBUS
{ SIGBUS, MP_handle_sig_bus },
#endif
#ifdef SIGFPE
{ SIGFPE, MP_handle_sig_fpe },
#endif
#ifdef SIGSEGV
{ SIGSEGV, MP_handle_sig_segv },
#endif
#ifdef SIGPIPE
{ SIGPIPE, MP_handle_sig_pipe },
#endif
{ -1, NULL }
};
void
MP_handle_timeout(void)
{
#if defined(MR_HAVE_DIRENT_H) && defined(MR_HAVE_OPENDIR) \
&& defined(MR_HAVE_READDIR) && defined(MR_HAVE_CLOSEDIR)
DIR *dir;
struct dirent *dirent;
int matchlen;
MR_bool success;
if (MP_timeout_want_dir == NULL || MP_timeout_want_prefix == NULL) {
MR_fatal_error(""MP_handle_timeout: null dir or prefix"");
}
matchlen = strlen(MP_timeout_want_prefix);
success = MP_do_try_get_lock(MP_timeout_mutex_file);
if (! success) {
/*
** We could not get the lock, so some other process holds it.
** We therefore abort the timeout, but schedule the next one.
*/
(void) alarm(MP_timeout_seconds);
return;
}
dir = opendir(MP_timeout_want_dir);
if (dir == NULL) {
MR_fatal_error(""MP_handle_timeout: opendir failed"");
}
while ((dirent = readdir(dir)) != NULL) {
if (MR_strneq(dirent->d_name, MP_timeout_want_prefix,
matchlen))
{
/* abort the timeout */
(void) closedir(dir);
(void) alarm(MP_timeout_seconds);
return;
}
}
(void) closedir(dir);
/*
** This call will delete the mutex file last, releasing the mutex
*/
MP_delete_cleanup_files();
exit(EXIT_SUCCESS);
#else
/* if we get here, the deep profiler isn't enabled */
#endif
}
void
MP_handle_sig_term(void)
{
MP_delete_cleanup_files_and_exit_failure(""SIGTERM"");
}
void
MP_handle_sig_hup(void)
{
MP_delete_cleanup_files_and_exit_failure(""SIGHUP"");
}
void
MP_handle_sig_int(void)
{
MP_delete_cleanup_files_and_exit_failure(""SIGINT"");
}
void
MP_handle_sig_quit(void)
{
MP_delete_cleanup_files_and_exit_failure(""SIGQUIT"");
}
void
MP_handle_sig_ill(void)
{
MP_delete_cleanup_files_and_exit_failure(""SIGILL"");
}
void
MP_handle_sig_abrt(void)
{
MP_delete_cleanup_files_and_exit_failure(""SIGABRT"");
}
void
MP_handle_sig_bus(void)
{
MP_delete_cleanup_files_and_exit_failure(""SIGBUS"");
}
void
MP_handle_sig_fpe(void)
{
MP_delete_cleanup_files_and_exit_failure(""SIGFPE"");
}
void
MP_handle_sig_segv(void)
{
MP_delete_cleanup_files_and_exit_failure(""SIGSEGV"");
}
void
MP_handle_sig_pipe(void)
{
MP_delete_cleanup_files_and_exit_failure(""SIGPIPE"");
}
MR_bool
MP_do_try_get_lock(const char *mutex_file)
{
int res;
MR_bool success;
res = open(mutex_file, O_CREAT | O_EXCL, 0);
if (res >= 0) {
(void) close(res);
MP_register_cleanup_file(mutex_file);
success = MR_TRUE;
} else if (res < 0 && errno == EEXIST) {
success = MR_FALSE;
} else {
MR_fatal_error(""MP_do_try_get_lock failed"");
}
return res;
}
void
MP_do_get_lock(const char *mutex_file)
{
int res;
for (;;) {
res = open(mutex_file, O_CREAT | O_EXCL, 0);
if (res >= 0) {
(void) close(res);
MP_register_cleanup_file(mutex_file);
return;
} else if (res < 0 && errno == EEXIST) {
sleep(5);
continue;
} else {
MR_fatal_error(""MP_do_get_lock failed"");
}
}
}
void
MP_do_release_lock(const char *mutex_file)
{
MP_unregister_cleanup_file(mutex_file);
(void) unlink(mutex_file);
}
").
:- pragma foreign_proc("C",
register_file_for_cleanup(File::in, S0::di, S::uo),
[will_not_call_mercury, promise_pure],
"
MP_register_cleanup_file(File);
S = S0;
").
:- pragma foreign_proc("C",
unregister_file_for_cleanup(File::in, S0::di, S::uo),
[will_not_call_mercury, promise_pure],
"
MP_unregister_cleanup_file(File);
S = S0;
").
:- pragma foreign_proc("C",
unregister_all_files_for_cleanup(S0::di, S::uo),
[will_not_call_mercury, promise_pure],
"
MP_cleanup_file_next = 0;
S = S0;
").
:- pragma foreign_proc("C",
delete_cleanup_files(S0::di, S::uo),
[will_not_call_mercury, promise_pure],
"
MP_delete_cleanup_files();
S = S0;
").
:- pragma foreign_proc("C",
setup_signals(MutexFile::in, WantDir::in, WantPrefix::in,
S0::di, S::uo),
[will_not_call_mercury, promise_pure],
"
int i;
MP_timeout_mutex_file = MutexFile;
MP_timeout_want_dir = WantDir;
MP_timeout_want_prefix = WantPrefix;
for (i = 0; MP_signal_structs[i].MP_signum >= 0; i++) {
MR_setup_signal(MP_signal_structs[i].MP_signum,
MP_signal_structs[i].MP_handler, MR_FALSE,
""Mercury deep profiler: cannot setup signal exit"");
}
/*
** Mercury exceptions do not cause signals. The default exception
** handler prints and error message and exits. To ensure that
** we delete up the files we need to clean up, we get the exit
** library function to invoke MP_delete_cleanup_files through
** MP_handle_fatal_exception.
*/
MR_register_exception_cleanup(MP_handle_fatal_exception, NULL);
S = S0;
").
:- pragma foreign_proc("C",
setup_timeout(Minutes::in, S0::di, S::uo),
[will_not_call_mercury, promise_pure],
"
MP_timeout_seconds = Minutes * 60;
(void) alarm(MP_timeout_seconds);
S = S0;
").
%-----------------------------------------------------------------------------%
get_lock(Debug, MutexFile) -->
(
{ Debug = yes }
;
{ Debug = no },
do_get_lock(MutexFile)
).
release_lock(Debug, MutexFile) -->
(
{ Debug = yes }
;
{ Debug = no },
do_release_lock(MutexFile)
).
:- pred do_get_lock(string::in, io__state::di, io__state::uo) is det.
:- pragma foreign_proc("C",
do_get_lock(MutexFile::in, S0::di, S::uo),
[will_not_call_mercury, promise_pure, tabled_for_io],
"
MP_do_get_lock(MutexFile);
S = S0;
").
:- pred do_release_lock(string::in, io__state::di, io__state::uo)
is det.
:- pragma foreign_proc("C",
do_release_lock(MutexFile::in, S0::di, S::uo),
[will_not_call_mercury, promise_pure, tabled_for_io],
"
MP_do_release_lock(MutexFile);
S = S0;
").
:- pragma foreign_proc("C",
make_want_file(WantFileName::in, S0::di, S::uo),
[will_not_call_mercury, promise_pure],
"
int fd;
fd = open(WantFileName, O_CREAT, 0);
if (fd < 0) {
MR_fatal_error(""make_want_file: open failed"");
}
(void) close(fd);
MP_register_cleanup_file(WantFileName);
S = S0;
").
:- pragma foreign_proc("C",
remove_want_file(WantFileName::in, S0::di, S::uo),
[will_not_call_mercury, promise_pure],
"
MP_unregister_cleanup_file(WantFileName);
(void) unlink(WantFileName);
S = S0;
").
--------------------------------------------------------------------------
mercury-reviews mailing list
post: mercury-reviews at cs.mu.oz.au
administrative address: owner-mercury-reviews at cs.mu.oz.au
unsubscribe: Address: mercury-reviews-request at cs.mu.oz.au Message: unsubscribe
subscribe: Address: mercury-reviews-request at cs.mu.oz.au Message: subscribe
--------------------------------------------------------------------------
More information about the reviews
mailing list