4

I've coded some relatively simple communication protocol using shared memory, and shared mutexes. But then I wanted to expand support to communicate between two .dll's having different run-time in use. It's quite obvious that if you have some std::vector<__int64> and two dll's - one vs2010, one vs2015 - they won't work politely with each other. Then I've thought - why I cannot serialize in ipc manner structure on one side and de-serialize it on another - then vs run-times will work smoothly with each other.

Long story short - I've created separate interface for sending next chunk of data and for requesting next chunk of data. Both are working while decoding happens - meaning if you have vector with 10 entries, each string 1 Mb, and shared memory is 10 Kb - then it would require 1*10*1024/10 times to transfer whole data. Each next request is followed by multiple outstanding function calls - either by SendChunk or GetNextChunk depending on transfer direction.

Now - I wanted to encode and decode happen simultaneously but without any threading - then I've came up with solution of using setjmp and longjmp. I'm attaching part of code below, just for you to get some understanding of what is happening in whole machinery.

#include "..."
#include <setjmp.h>                     //setjmp

class Jumper: public IMessageSerializer
{
public:
    char lbuf[ sizeof(IpcCommand) + 10 ];
    jmp_buf     jbuf1;
    jmp_buf     jbuf2;
    bool        bChunkSupplied;

    Jumper() :
        bChunkSupplied(false)
    {
        memset( lbuf, 0 , sizeof(lbuf) );
    }

    virtual bool GetNextChunk( bool bSend, int offset )
    {
        if( !bChunkSupplied )
        {
            bChunkSupplied = true;
            return true;
        }

        int r = setjmp(jbuf1);
        ((_JUMP_BUFFER *)&jbuf1)->Frame = 0;

        if( r == 0 )
            longjmp(jbuf2, 1);

        bChunkSupplied = true;
        return true;
    }

    virtual bool SendChunk( bool bLast )
    {
        bChunkSupplied = false;
        int r = setjmp(jbuf2);
        ((_JUMP_BUFFER *)&jbuf2)->Frame = 0;
        if( r == 0 )
            longjmp(jbuf1, 1);

        return true;
    }

    bool FlushReply( bool bLast )
    {
        return true;
    }

    IpcCommand* getCmd( void )
    {
        return (IpcCommand*) lbuf;
    }

    int bufSize( void )
    {
        return 10;
    }
}; //class Jumper

Jumper jumper;

void main(void)
{
    EncDecCtx enc(&jumper, true, true);
    EncDecCtx dec(&jumper, false, false);
    CString s;

    if( setjmp(jumper.jbuf1) == 0 )
    {
        alloca(16*1024);
        enc.encodeString(L"My testing my very very long string.");
        enc.FlushBuffer(true);
    } else {
        dec.decodeString(s);
    }

    wprintf(L"%s\r\n", s.GetBuffer() );
}

There are couple of issues here. After first call to setjmp I'm using alloca() - which allocates memory from stack, it will be autofreed on return. alloca can happen only during first jump, because any function call always uses callstack (to save return address) and it can corrupt second "thread" call stack.

There are multiple articles discussing about how dangerous setjmp and longjmp are, but this is now somehow working solution. The stack size (16 Kb) is reservation for next function calls to come - decodeString and so on - it can be adjusted to bigger if not enough.

After trying out this code I've noticed that x86 code was working fine, but 64-but did not work - I've got similar problem to what is described here:

An invalid or unaligned stack was encountered during an unwind operation

Like article suggested I've added ((_JUMP_BUFFER *)&jbuf1)->Frame = 0; kind of resetting - and after that 64-bit code started to work. Currently library is not using any exception mechanism and I'm not planning to use any (will try-catch everything if needed in encode* decode* function calls.

So questions:

  • Is it acceptable solution to disable unwinding in code ? (((_JUMP_BUFFER *)&jbuf1)->Frame = 0;) What unwinding really means in context setjmp / longjmp ?

  • Do you see any potential problem with given code snipet?

Community
  • 1
  • 1
TarmoPikaro
  • 4,723
  • 2
  • 50
  • 62
  • 2
    I'm not debugging `setjmp/longjmp` code; I have yet to find a problem for which it's a worthwhile approach. These kind of problems just reinforce that assessment. This is very much self-inflicted pain. – MSalters Sep 20 '16 at 20:45
  • 1
    Yes, people are afraid of in-depth knowledge. Without it you need to create two threads, interprocess communication channels, shared memory and so on. Everything is doable in theory - it's a matter how much developer wants to go deeper. Anyway - lets me practical here - I don't want here get what you feel about this code, but practical knowledge on subject. – TarmoPikaro Sep 20 '16 at 20:50
  • 1
    Different philosophies in coding at play here, not fear of experimentation. – user4581301 Sep 20 '16 at 21:10
  • Now, I'm not very familiar with `setjmp()` and `longjmp()` (with basically all I know about them being reasons _not_ to use them) but if you need coroutines, would it be possible to use `boost::coroutine`, the C++17 coroutine TS, or the [COROUTINE](http://www.akira.ruc.dk/~keld/research/COROUTINE/) library instead? Or am I misunderstanding something here? – Justin Time - Reinstate Monica Sep 20 '16 at 21:39
  • Hmm... Checked coroutines, looks like interesting idea, but isn't my approach a little bit simpler than coroutines ? Basically you need to separately code that coroutine, and somehow test against coroutine API's - I'm providing similar mechanism, with less code. Of course there is a risk that something will not work - but similar thing exists in coroutines ? – TarmoPikaro Sep 21 '16 at 03:36
  • "Currently library is not using any exception mechanism" -- I hope you're not allocating memory, then, since those calls can throw, and that's not the only place there can be implicit exceptions. I also hope you're not relying on destructors, either. As of this writing, this question is tagged [c++] and [setjmp], which is basically a contradiction in terms. – eh9 Oct 25 '16 at 16:53
  • My intention is not to throw - if there is memory allocation happening, I'll add try-catch to it. – TarmoPikaro Oct 25 '16 at 17:57
  • `but isn't my approach a little bit simpler than coroutines` seeing the issues you have now I would probably disagree. I don't see anything in the current code but you should check your types. It seems like somewhere you cast a pointer to the jumps wrong. Keep in mind pointers are double the size in 64 bit so you should check there. And put the warning level up to maximum and walk through them. They maybe show you the error. – Hayt Oct 28 '16 at 14:30
  • This code is actually working and also it's much less code compared to coroutines. So I'm not looking for error in this code, but interested what means unwinding / exception handling in context of setjmp / longjmp. – TarmoPikaro Oct 28 '16 at 15:27

0 Answers0