Rocksolid Light

Welcome to novaBBS (click a section below)

mail  files  register  newsreader  groups  login

Message-ID:  

6 May, 2024: The networking issue during the past two days has been identified and appears to be fixed. Will keep monitoring.


devel / comp.lang.c / Re: scopped lock in plain C (similar to std::lock_guard in C++)

SubjectAuthor
* scopped lock in plain C (similar to std::lock_guard in C++)Kirill Frolov
`- Re: scopped lock in plain C (similar to std::lock_guard in C++)Kaz Kylheku

1
scopped lock in plain C (similar to std::lock_guard in C++)

<GJwtJ.486637$ptt8.446497@fx06.ams1>

  copy mid

https://www.novabbs.com/devel/article-flat.php?id=19378&group=comp.lang.c#19378

  copy link   Newsgroups: comp.lang.c
Path: i2pn2.org!i2pn.org!aioe.org!news.uzoreto.com!feeder1.feed.usenet.farm!feed.usenet.farm!peer01.ams4!peer.am4.highwinds-media.com!news.highwinds-media.com!peer02.ams1!peer.ams1.xlned.com!news.xlned.com!fx06.ams1.POSTED!not-for-mail
Newsgroups: comp.lang.c
From: fk0...@fk0.name (Kirill Frolov)
Subject: scopped lock in plain C (similar to std::lock_guard in C++)
Organization: I'am organized.
Reply-To: fk0@fk0.name
User-Agent: GoldED+/W32 1.1.4.7
User-Agent: slrn/1.0.3 (Linux)
Lines: 208
Message-ID: <GJwtJ.486637$ptt8.446497@fx06.ams1>
X-Complaints-To: abuse@blocknews.net
NNTP-Posting-Date: Mon, 13 Dec 2021 00:46:30 UTC
Date: Mon, 13 Dec 2021 00:46:30 GMT
X-Received-Bytes: 8803
 by: Kirill Frolov - Mon, 13 Dec 2021 00:46 UTC

Hello...

I want to discuss the possibility of the implementation of some
programming technique, which allows to use synchronization primitives,
especially mutexes, seamlessly, like this can be done in C++ with
std::lock_guard.

The idea is that manual operating on mutex is inconvenient and error
prone, so it was be desirable to have some blocks of code which executed
with specified mutex locked, but at same time where shold be no
necessity to manually lock and especially to unlock the mutex (which can be
forgotten). And of course, in case if code block is leaved by some sort
of "goto" operators (break, continue, return), mutex still must be
unlocked automatically.

Basically desired syntax must have the form:

// mutex isn't locked here
...
{
DECLARE_SCOPED_LOCK(mutex); // mutex locked now

perform operations with mutex locked...

if (condition)
break/continue/return/goto...

} // mutes unlocked here

Or the following form might be acceptable too:

DECLARE_SCOPED_LOCK(mutex); // mutex locked now
{
perform operations with locked mutex...

if (condition)
break/continue/return/goto...

} // mutex unlocked here

First, as I understood, there is no way to avoid of "gotos" from
any point in code to any other point. And using of "goto" violates
principles of structure programming, moving us to fortran-like spaghetty.
So we assume, that "goto" operator must have limited usage.

Second, is the question how to execute function which unlocks the mutex
at end of the block. The only way which C language allows, as I think,
is the use of "for" operator, like this:

for (mutex_lock(&mutex); one-time-condition; mutex_unlock(&mutex))
{
operations with locked mutex...
}

"one-time-condition" here is any condition which assures, that loop
body executes exactly one time. In this case "break" and "continue"
operators work as expected (allows to break the loop and unlock
mutex on the exit).

The "return" operator is the problem. It can appear almost in any place
and just silently finishes the function. And comparing with "goto" it is
really dangerrous. If "goto", as said above, must have limited usage,
"return" operator might be met anythere in the code.

One possible solution is to redefine "return" with macroprocessor,
and substitute it with some other operator. But such approach arises
another problem, that "return" might be used in very different contexts
and such substitution will be inapropriate (and even dangerrous) in
other contexts. Substitution must not break other code.

I have found possible solution, which allows to stop compilation,
if "return" meets in the block of code related to scoped lock,
and which have no any effects at all in other contexts. The idea
is that "return" operator might be defined like this:

#define return return (void)sizeof((some_type*)some_var),

And in same header file, where "return" operation redefined, variable
"some_var" (of type "some_type*") and the type "some_type" must be declared.

Left part of comma operator have no any effect on return value (which is
right part of comma operator). Moreover, left part even not generates
any runtime code, it is evaluated solely in compile time. So such
substitution of "return" operator doesn't pose any danger any other code.

But in context of scoped lock, the variable "some_var" might be defined
with other type (so it shadows file scoped variable). And this gives an
error in case if "return" operator meets in the body of scoped lock,
because the types (in the arguments of sizeof operator) are different and
not compatible now.

So, now we have a method to prevent use "return" operator in the block
of code related to scoped lock. And this gives us guarantee, that lock
always will be unlocked (except of cases, when code uses "goto" or
"longjmp", which is not typical cases).

But if programmer still wants to return from the function now, but not
finish all multiple nested scoped locks, we can provide special macros
for this, which can be used in place of "return" operator for scoped
locks. This is not hard, and you will see it in full example below.

So, it's time to unveil complete solution -- see below.

I requesting for your comments, improvements, critics, suggestions.

#include <stdio.h>
#include <stddef.h>

/* Mutex object imitation, just for example. */
struct mutex {
int v;
};

void mutex_lock(struct mutex *m)
{ (void)m;
printf("mutex %p locked\n", (void*)m);
}

void mutex_unlock(struct mutex *m)
{ (void)m;
printf("mutex %p unlocked\n", (void*)m);
}

/* This variable must exist in global namespace to implement "return" macros (see below).
* This definition must be placed in some header file to be available at file scope level. */
static const struct _can_return { int dummy; } *_can_return;

/* Avoid warning about unused "_can_return" symbol. */
struct _can_return_unused { int dummy[sizeof(_can_return)]; };

/* Linked list of all locked mutexes in SCOPED_LOCKs within single function. */
struct _scope {
const struct _scope *next;
struct mutex *mutex;
};

/* Last element within linked list shown above (must exist and file scope level.
* This definition must be placed in some header file to be available at file scope level. */
static const struct _scope *const _scope = NULL;

/* avoid warning about unused "_scope" symbol */
struct _scope_unused { int dummy[sizeof(_scope)]; };

/* This macros declares, that following operator (or operators block) will
* be executed with locked specified mutex, which will be unlocked automaticaly
* on leaving following operator or block (see example below). */
#define SCOPED_LOCK(MutexPtr) \
for (struct _scope _this_scope = {_scope, (mutex_lock(MutexPtr), MutexPtr)}, *_scope = &_this_scope; \
_scope->mutex != NULL; \
mutex_unlock(_scope->mutex), _scope->mutex = NULL) \
for (struct { int i; } _can_return = {0}; !_can_return.i; _can_return.i = 1)

/* This macros prevents using of "return" operator in the in the block followoing
* SCOPED_LOCK keyword. In this case compilation finished with an error
* (due to incompatible types). In other contexts "return" operator works as usual
* and this modification (left part of comma operator) have no effect. The idea is
* that in SCOPED_LOCK context and at file scope "_can_return" symbol have different
* types, and type of "_can_return" symbol in SCOPED_LOCK context causes compilation
* error. */
#define return return (void)sizeof((struct _can_return *)_can_return),

/* This macros might be used in code block following SCOPED_LOCK as alternative to
* "return" operator. It allows to return from all nested scoped locks and unlock
* all previously locked mutexes. Where is no problem if this macros
* will be used outside of SCOPED_LOCK -- this not causes an error. */
#define SCOPE_RETURN(val) \
do { \
const struct _scope *scope = _scope; \
while (scope && scope->mutex) \
mutex_unlock(scope->mutex), scope = scope->next; \
struct _can_return *_can_return = NULL; \
return (val); \
} while(0);

/* This example shows how SCOPED_LOCK macros can be used. */
int main()
{ struct mutex m1 = {0};
struct mutex m2 = {1};

SCOPED_LOCK(&m1)
{
SCOPED_LOCK(&m2)
{
if (0) {
break; // "break" operator allows to exit from inner SCOPED_LOCK
}

if (0) {
// return 1; // this gives compilation error
SCOPE_RETURN(2);
}
}
}

SCOPE_RETURN(3);

return 0;
}

Re: scopped lock in plain C (similar to std::lock_guard in C++)

<20211216232125.619@kylheku.com>

  copy mid

https://www.novabbs.com/devel/article-flat.php?id=19475&group=comp.lang.c#19475

  copy link   Newsgroups: comp.lang.c
Path: i2pn2.org!i2pn.org!eternal-september.org!reader02.eternal-september.org!.POSTED!not-for-mail
From: 480-992-...@kylheku.com (Kaz Kylheku)
Newsgroups: comp.lang.c
Subject: Re: scopped lock in plain C (similar to std::lock_guard in C++)
Date: Fri, 17 Dec 2021 07:36:20 -0000 (UTC)
Organization: A noiseless patient Spider
Lines: 53
Message-ID: <20211216232125.619@kylheku.com>
References: <GJwtJ.486637$ptt8.446497@fx06.ams1>
Injection-Date: Fri, 17 Dec 2021 07:36:20 -0000 (UTC)
Injection-Info: reader02.eternal-september.org; posting-host="9b1ceb6f3526b28e96cf57cd05c04e77";
logging-data="16769"; mail-complaints-to="abuse@eternal-september.org"; posting-account="U2FsdGVkX1+qUTNSBR8PpXeNOJ9hvc47FlF15NwbyYw="
User-Agent: slrn/1.0.3 (Linux)
Cancel-Lock: sha1:GwswJchbMvjAJVIILwwtKkFD+I4=
 by: Kaz Kylheku - Fri, 17 Dec 2021 07:36 UTC

On 2021-12-13, Kirill Frolov <fk0@fk0.name> wrote:
> Hello...
>
> I want to discuss the possibility of the implementation of some
> programming technique, which allows to use synchronization primitives,
> especially mutexes, seamlessly, like this can be done in C++ with
> std::lock_guard.

A good way to do this kind of thing is exemplified in the POSIX threads
API with thread cleanup handler management macros:

pthread_cleanup_push(function, arg);

/* if thread is cancelled here, function(arg) gets called. */

pthread_cleanup_pop(1); /* 1 -> call function(arg); 0 -> don't */

The push and pop calls have to pair.

(The Boolean argument in the pop call is clever; there are scenarios when
you don't want to call the cleanup function, or conditionally call it
based on the value of some expression.)

[snip baroque ideas]

#define LOCK(mutex) { mutex_lock(&(mutex))

#define UNLOCK(mutex) mutex_unlock(&(mutex)); }

That's the basic idea. Of course, it's hardly fool proof, e.g:

LOCK(mutex)

} /* matches open brace in LOCK */

Firstly, your editor will catch this; when you try to reindent the code,
it turns to shit. Secondly, there are a few things you can do to reduce
this threat and improve it in other ways, though I wouldn't go to too
many lengths. One simple idea:

#define LOCK(mutex) do { mutex_lock(&(mutex))

#define UNLOCK(mutex) mutex_unlock(&(mutex)); } while (0)

Now you can't close a LOCK(mutex) with just a curly brace, because
do { } is a syntax error without a while (...);.

If you want access to the mutex_lock return value, you have
to do it otherwise, like put the lock call outside of the braces:

--
TXR Programming Language: http://nongnu.org/txr
Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal

1
server_pubkey.txt

rocksolid light 0.9.81
clearnet tor