[m-dev.] For review: coroutining changes
Fergus Henderson
fjh at cs.mu.OZ.AU
Wed Nov 25 16:28:38 AEDT 1998
On 25-Nov-1998, Thomas Charles CONWAY <conway at cs.mu.OZ.AU> wrote:
> Improvements to coroutining support. These changes allow us to do
> provide io primatives that cause the Mercury context to suspend rather
> than causing the engine to block.
> +++ pragma_c_gen.m 1998/11/22 22:45:41
> @@ -382,14 +382,20 @@
> %
> % Code fragments to obtain and release the global lock
> %
> + code_info__get_module_info(ModuleInfo),
> { ThreadSafe = thread_safe ->
> ObtainLock = pragma_c_raw_code(""),
> ReleaseLock = pragma_c_raw_code("")
> ;
> - ObtainLock =
> - pragma_c_raw_code("\tMR_OBTAIN_GLOBAL_C_LOCK();\n"),
> - ReleaseLock =
> - pragma_c_raw_code("\tMR_RELEASE_GLOBAL_C_LOCK();\n")
> + module_info_pred_proc_info(ModuleInfo, PredId, ProcId,
> + PredInfo, _),
If you don't need to proc_info, call module_info_pred_info
rather than module_info_pred_proc_info.
> @@ -447,7 +453,7 @@
> % join all the components of the pragma_c together
> %
> { Components = [InputComp, SaveRegsComp, ObtainLock, C_Code_Comp,
> - CheckR1_Comp, ReleaseLock, RestoreRegsComp,
> + ReleaseLock, CheckR1_Comp, RestoreRegsComp,
> OutputComp] },
> { PragmaCCode = node([
> pragma_c(Decls, Components, MayCallMercury, MaybeFailLabel, no)
The log message doesn't mention that bug fix.
> + /*
> + ** Check to see if any contexts that blocked on IO have become
> + ** runnable. Return the number of contexts that are still blocked.
> + ** The parameter specifies whether or not the call to select should
> + ** block or not.
> + */
> +static int check_pending_contexts(Bool block);
> +static int
> +check_pending_contexts(Bool block)
> +{
You should delete the first declaration of `check_pending_contexts',
and unindent the comment.
> + int err, n;
> + fd_set rd, wr, ex;
> + struct timeval timeout;
`fd_set' and `struct timeval' are not ANSI C, so this stuff should be
#ifdef'd and autoconf'd.
I would much prefer it if you would use more meaningful variable names
throughout this function.
E.g. perhaps `max_fd' instead of `n', and perhaps `read_fds' instead
of `rd', and something instead of `tmp'.
> + MR_PendingContext *tmp;
> +
> + if (MR_pending_contexts == NULL)
> + return 0;
Add { } around the if body here please.
> + MR_fd_zero(&rd); MR_fd_zero(&wr); MR_fd_zero(&ex);
> + n = -1;
> + for (tmp = MR_pending_contexts ; tmp ; tmp = tmp -> next) {
s/ ;/;/g
> + if (tmp->waiting_mode & MR_PENDING_READ) {
> + n = ((n > tmp->fd) ? n : tmp->fd);
That would be simpler if written as
if (n > tmp->fd) n = tmp->fd;
or
n = MR_MAX(n, tmp->fd);
> + if (n == 0)
> + fatal_error("no fd's set!");
Please use { } here.
> + if (block) {
> + err = select(n, &rd, &wr, &ex, NULL);
> + } else {
> + timeout.tv_sec = 0;
> + timeout.tv_usec = 0;
> + err = select(n, &rd, &wr, &ex, &timeout);
> + }
> +
> + if (err < 0)
> + fatal_error("select failed!");
Likewise.
> + n = 0;
> + for (tmp = MR_pending_contexts ; tmp ; tmp = tmp -> next) {
> + n++;
> + if (tmp->waiting_mode & MR_PENDING_READ) {
> + if(FD_ISSET(tmp->fd, &rd)) {
> + schedule(tmp->context);
> + continue;
> + }
> + }
> + if (tmp->waiting_mode & MR_PENDING_WRITE) {
> + if(FD_ISSET(tmp->fd, &wr)) {
> + schedule(tmp->context);
> + continue;
> + }
> + }
> + if (tmp->waiting_mode & MR_PENDING_EXEC) {
> + if(FD_ISSET(tmp->fd, &ex)) {
> + schedule(tmp->context);
> + continue;
> + }
> + }
IMHO this would be better written using `&&' rather than nested ifs:
if ((tmp->waiting_mode & MR_PENDING_WRITE)
&& FD_ISSET(tmp->fd, &wr))
{
schedule(tmp->context);
continue;
}
if (...
It might also be better to use "else if" rather than "continue":
if ((tmp->waiting_mode & MR_PENDING_WRITE)
&& FD_ISSET(tmp->fd, &wr))
{
schedule(tmp->context);
} else if ((tmp->waiting_mode & MR_PENDING_READ)
&& FD_ISSET(tmp->fd, &rd))
{
schedule(tmp->context);
} else if ((tmp->waiting_mode & MR_PENDING_EXEC)
&& FD_ISSET(tmp->fd, &ex))
{
schedule(tmp->context);
}
Or in fact to use `||':
if ( ((tmp->waiting_mode & MR_PENDING_WRITE)
&& FD_ISSET(tmp->fd, &wr))
|| ((tmp->waiting_mode & MR_PENDING_READ)
&& FD_ISSET(tmp->fd, &rd))
|| ((tmp->waiting_mode & MR_PENDING_EXEC)
&& FD_ISSET(tmp->fd, &ex))
)
{
schedule(tmp->context);
}
Yep, on reflection I like this one best.
> - if (depth > 0 && tmp->owner_thread == thd
> - || tmp->owner_thread == NULL)
> + if ((depth > 0 && tmp->owner_thread == thd)
> + || (tmp->owner_thread == (MercuryThread) NULL))
> break;
Use { ... } around the if body, please.
if (...
|| ...)
{
break;
}
> - if (MR_runqueue_head == NULL)
> + if (MR_runqueue_head == NULL && MR_pending_contexts == NULL)
> fatal_error("empty runqueue!");
> +
> + while (MR_runqueue_head == NULL)
> + check_pending_contexts(TRUE); /* block */
Use { } around if and while bodies, please.
> +/*
> +** As well as the runqueue, we maintain a linked list of contexts
> +** and associated file descriptors that are suspended blocked for
> +** reads/writes/exceptions. When the runqueue becomes empty, if
> +** this list is not empty then we call select and block until one
> +** or more of the file descriptors become ready for I/O, then
> +** wake the appropriate context.
> +** In addition, we periodically check to see if the list of blocked
> +** contexts is non-empty and if so, poll to wake any contexts that
> +** can unblock. This, while not yielding true fairness (since this
> +** requires the current context to perform some yield-like action),
> +** ensures that it is possible for programmers to write concurrent
> +** programs with continuous computation and interleaved I/O dependent
> +** computation in a straight-forward manner.
> +*/
Is the second paragraph implemented yet? If not, you should make
that clear.
> +#define MR_PENDING_READ 0x01
> +#define MR_PENDING_WRITE 0x02
> +#define MR_PENDING_EXEC 0x04
How about
enum {
MR_PENDING_READ = 0x01,
MR_PENDING_WRITE = 0x02,
MR_PENDING_EXEC = 0x04
};
?
In general it's better to avoid #define if possible.
> - #define MR_OBTAIN_GLOBAL_C_LOCK() MR_mutex_lock(&MR_global_lock, \
> - "pragma c code");
> + #define MR_OBTAIN_GLOBAL_C_LOCK(where) MR_LOCK(&MR_global_lock, \
> + where);
>
> - #define MR_RELEASE_GLOBAL_C_LOCK() MR_mutex_unlock(&MR_global_lock, \
> - "pragma c code");
> + #define MR_RELEASE_GLOBAL_C_LOCK(where) MR_UNLOCK(&MR_global_lock, \
> + where);
All uses of the macro parameter `where' should be in parentheses.
> -begin-mercury_select.c----------------------------------------------------
> #include "mercury_select.h"
>
> void
> MR_fd_zero(fd_set *fdset)
> {
> FD_ZERO(fdset);
> }
>
> -end-mercury_select.c----------------------------------------------------
> -begin-mercury_select.h----------------------------------------------------
>
> #ifndef MR_MERCURY_SELECT_H
> #define MR_MERCURY_SELECT_H
>
> #include <sys/time.h>
> #include <sys/types.h>
> #include <unistd.h>
>
> void MR_fd_zero(fd_set *fdset);
>
> #endif
> -end-mercury_select.h----------------------------------------------------
Documentation, please!
That stuff should probably go in mercury_misc.{c.h}, alongside
`MR_memcpy()', since it exists for very similar reasons.
It is also non-portable and hence should be #ifdef'd and autoconf'd.
----------
There were a number of changes which were not documented in the log message,
including the changes to mercury_types.h and mercury_library_types.h.
Apart from the points raised above, it looks fine.
But I think it needs another round of reviewing.
Cheers,
Fergus.
--
Fergus Henderson <fjh at cs.mu.oz.au> | "Binaries may die
WWW: <http://www.cs.mu.oz.au/~fjh> | but source code lives forever"
PGP: finger fjh at 128.250.37.3 | -- leaked Microsoft memo.
More information about the developers
mailing list