6

While implementing a communication protocol, we have an encoder that traverses some structs recursively and encodes them into a binary message.

So far so good, but now the buffer has to split out into multiple chunks of fixed size, e.g. the upper size of receiving buffer. Since allocating memory for the full message and cutting it consequently seems to be too wasteful (the size of the message is --in theory-- not bounded), the idea is now to implement a coroutine with means of setjmp/longjmp.

At the moment, I have a prototype with two jump buffers - one buffer for resuming the encode function and the second one for simulating the return behavior of the function to jump back to its caller.

Well, it seems to work, but the code looks like coming straight from hell. Are there any 'conventions' for implementing interruptible recursive functions, maybe a set of macros or something? I would like to use only standardized functions, no inline asm in order to stay portable.

Addition: The prototype is here: https://github.com/open62541/open62541/compare/master...chunking_longjmp The 'usage' is shown inside of the unit-test. Currently, coroutine behavior is implemented for a non-recursive function Array_encodeBinary. However, the 'coroutine' behavior should be extended to the general recursive UA_encodeBinary function located here: https://github.com/open62541/open62541/blob/master/src/ua_types_encoding_binary.c#L1029

Stasik
  • 2,568
  • 1
  • 25
  • 44
  • It looks like you are ready to switch to the Go programming language. – chqrlie Dec 29 '15 at 22:24
  • It sounds like you could use the producer consumer pattern. – Millie Smith Dec 29 '15 at 22:27
  • 2
    The thing you are trying to do is not supported by ISO C. The way you're using `setjmp` and `longjmp` is not sanctioned by the standard, and there is no alternative in "portable" C. You may find [`setcontext`](http://linux.die.net/man/2/getcontext) and friends *somewhat* more congenial, since they are actually designed for this use case, but they are not available on all platforms and they are still rather awkward to use. – zwol Dec 29 '15 at 22:27
  • 2
    You can try and allocate a separate stack for the coroutine and use `setjmp` and `longjmp` to switch between your recursive co-routine and its consumer. Nothing portable in sight I'm afraid because you may have to fiddle with the `jmpbuf`s by hand. – chqrlie Dec 29 '15 at 22:28
  • 3
    Sounds like a bloat approach and/or an XY-problem. As given the question is too broad. What is your actual problem? Using recursive functions - even more as you write about wasting memory (is that an embedded system?) - is most times a bad idea. – too honest for this site Dec 29 '15 at 22:28
  • You need to explain why a `PutByte` function doesn't solve your problem. – user3386109 Dec 29 '15 at 23:05
  • 2
    Can you please post your existing code for this? No offense, but this sounds like a terrible hack [using `setjmp/longjmp`]. I've done this sort of thing before and I can think of a number of ways to do this that are clean but it would help to have some context to riff off of. – Craig Estey Dec 29 '15 at 23:08
  • 1
    A "binary [massage](https://en.wikipedia.org/wiki/Massage)" sounds relaxing right now, but I think this is about a "binary message". :-) – chux - Reinstate Monica Dec 29 '15 at 23:20
  • just have one process create the chunks as a linked list of fixed-sized memory blocks (see https://en.wikipedia.org/wiki/Hashed_array_tree), and have another process working concurrently that sends them, or whatever, releasing the memory of the processed chunk. or must it be single threaded? in that case, convert your recursion to iteration. it is always possible to do this, imitating the recursion's control stack as some dynamic data structure on the heap. – Will Ness Dec 29 '15 at 23:30
  • @chux: As a massage is better done carefully (otherwise it will either not work at all or is pure pain), I'm not sure if a **binary** massage really is that relaxing;-) – too honest for this site Dec 29 '15 at 23:48
  • Thank you for the feedback, links to code have been add, i am still curious why it is not supported by ISO C, @zwol. In my opinion I do something what chqrlie suggested - switching between the function and its caller. More feedback welcome. – Stasik Dec 30 '15 at 10:03
  • @stasik The short answer is that you're not allowed to use `longjmp` to re-enter a function that has returned (either normally or by calling `longjmp` itself). This is not just a theoretical problem; its stack frame is liable to be trashed. The point of using the `setcontext` functions instead is that you construct an entire additional *stack* and thus prevent the suspended context's stack frames from getting trashed. – zwol Dec 30 '15 at 18:02
  • @zwol a co-commiter proposed the following trick https://github.com/open62541/open62541/commit/272eca14a02842d5acd626e2913c40aaec504e8f#diff-29584f3d8203a83888051cf7cfee8b72R1473 just alloca to increase the current frame and hope that the intermediate function calls fit in... oh this is dirty... – Stasik Dec 30 '15 at 21:21
  • @Stasik You may be able to kludge something that works (on some platforms and compilers) but it will always be fragile. – zwol Dec 30 '15 at 21:41
  • @zwol so, you suggest to go for setcontext? this means a context switch of the system, correct? – Stasik Dec 30 '15 at 22:02
  • @stasik If you can assume a POSIX.1-2001 environment that still includes the functions deprecated in -2008, then yes, that would be the path of least resistance. I don't understand your second question. – zwol Dec 31 '15 at 00:03
  • @zwol the second queston was whether every call to setcontext implicates a context switch by the OS? – Stasik Jan 01 '16 at 15:05
  • @Stasik It depends on the implementation and on your definition of "context switch", I can't give you anything more specific than that. – zwol Jan 01 '16 at 16:39

1 Answers1

2

As pointed out by Olaf the easiest way would be to use an iterative algorithm. However, if for some reason this is difficult, you can always simulate the recursive algorithm with a stack container and a while loop. This at least makes the function easier to interrupt. Pretty good article of how to implement this can be found here. The article is written for c++, but it should not be difficult to convert it to c.

Ari Hietanen
  • 1,749
  • 13
  • 15
  • A software implementation likely would have the same problems as the normal (hardware) stack. Actually, it would add even more overhead. Anyway, I don't think that is an answer, but more of a comment. – too honest for this site Dec 29 '15 at 23:46