0

I got two threads (thrd_t): one main thread processing events and one child thread waiting for terminal input using fgetwc.

    #include <threads.h> // mtx_t, mtx_lock, mtx_unlock
    #include <wchar.h> // fgetwc, fgetwc_unlocked
    FILE* f = (FILE*) p2;
    mtx_t* m = (mtx_t*) p1;
    volatile sig_atomic_t* i = (volatile sig_atomic_t*) p0;
    mtx_lock(m);
    volatile wint_t c = fgetwc(f);
    if (c != WEOF) {
        ungetwc(c, f);
        copy_integer(p0, (void*) TRUE_BOOLEAN_STATE_CYBOI_MODEL);
    }
    mtx_unlock(m);
    while (*i != *FALSE_BOOLEAN_STATE_CYBOI_MODEL);

Since I want to read ansi escape code sequences (three characters), the terminal must not block after each character and is therefore set to non-blocking in the main thread using t.c_cc[VMIN]=0. After the sequence has been detected, I reset the terminal to blocking using t.c_cc[VMIN]=1, so that the child thread blocks at fgetwc until the next input gets detected.

    mtx_t* m = (mtx_t*) p3;
    int* i = (int*) p2;
    // The loop break flag.
    int b = *FALSE_BOOLEAN_STATE_CYBOI_MODEL;
    // The escape character flag.
    int esc = *FALSE_BOOLEAN_STATE_CYBOI_MODEL;
    int aec = *FALSE_BOOLEAN_STATE_CYBOI_MODEL;
    // The input character.
    wint_t c = WEOF;
    mtx_lock(m);
    // Set unblocking mode in terminal.
    edit_service(p4, (void*) FALSE_BOOLEAN_STATE_CYBOI_MODEL, (void*) FALSE_BOOLEAN_STATE_CYBOI_MODEL, (void*) FALSE_BOOLEAN_STATE_CYBOI_MODEL, (void*) TERMINAL_CYBOI_CHANNEL);
    while (*TRUE_BOOLEAN_STATE_CYBOI_MODEL) {
        if (b != *FALSE_BOOLEAN_STATE_CYBOI_MODEL) {
            break;
        }
        read_unix_terminal_character(p0, p1, (void*) &b, (void*) &esc, (void*) &aec, (void*) &c);
    }
    // Set blocking mode in terminal. VMIN = 1
    edit_service(p4, (void*) TRUE_BOOLEAN_STATE_CYBOI_MODEL, (void*) FALSE_BOOLEAN_STATE_CYBOI_MODEL, (void*) FALSE_BOOLEAN_STATE_CYBOI_MODEL, (void*) TERMINAL_CYBOI_CHANNEL);
    // Reset interrupt request in input/output entry.
    copy_integer(i, (void*) FALSE_BOOLEAN_STATE_CYBOI_MODEL);
    mtx_unlock(m);

Problem: Sometimes, fgetwc blocks, other times it does not. When a character sequence was read in (three characters), the reading loop is left at the third character and the child thread blocks fine. When a single character is read, then its end gets detected by a following WEOF, but the child thread does not block anymore, even though t.c_cc[VMIN]=1.

    wint_t* c = (wint_t*) p5;
    int* aec = (int*) p4;
    int* esc = (int*) p3;
    FILE* f = (FILE*) p1;
    *c = fgetwc(f);
    if (*c != WEOF) {
        if (*aec == *TRUE_BOOLEAN_STATE_CYBOI_MODEL) {
            // The character sequence ESC[ was received before.
            // This is the beginning of an ansi escape code sequence.
            // Reset ansi escape code flag.
            copy_integer(p4, (void*) FALSE_BOOLEAN_STATE_CYBOI_MODEL);
            // Append source character to destination item.
            // This is the actual ansi escape code.
            modify_item(p0, p5, (void*) WIDE_CHARACTER_TEXT_STATE_CYBOI_TYPE, (void*) FALSE_BOOLEAN_STATE_CYBOI_MODEL, (void*) PRIMITIVE_STATE_CYBOI_MODEL_COUNT, *NULL_POINTER_STATE_CYBOI_MODEL, (void*) VALUE_PRIMITIVE_STATE_CYBOI_NAME, (void*) TRUE_BOOLEAN_STATE_CYBOI_MODEL, (void*) APPEND_MODIFY_LOGIC_CYBOI_FORMAT);
            // Set loop break flag.
            // All values have been received, so that the loop can be left now.
            copy_integer(p2, (void*) TRUE_BOOLEAN_STATE_CYBOI_MODEL);
        } else if (*esc == *TRUE_BOOLEAN_STATE_CYBOI_MODEL) {
            // The escape character ESC was received before.
            // This might be the beginning of an ansi escape code.
            // Reset escape character flag.
            copy_integer(p3, (void*) FALSE_BOOLEAN_STATE_CYBOI_MODEL);
            if (*c == *((wint_t*) LEFT_SQUARE_BRACKET_UNICODE_CHARACTER_CODE_MODEL)) {
                // The escape character ESC received before
                // is followed by an opening square bracket [.
                // This is the beginning of an ansi escape code.
                // Set ansi escape code flag.
                copy_integer(p4, (void*) TRUE_BOOLEAN_STATE_CYBOI_MODEL);
                // Append source character to destination item.
                modify_item(p0, p5, (void*) WIDE_CHARACTER_TEXT_STATE_CYBOI_TYPE, (void*) FALSE_BOOLEAN_STATE_CYBOI_MODEL, (void*) PRIMITIVE_STATE_CYBOI_MODEL_COUNT, *NULL_POINTER_STATE_CYBOI_MODEL, (void*) VALUE_PRIMITIVE_STATE_CYBOI_NAME, (void*) TRUE_BOOLEAN_STATE_CYBOI_MODEL, (void*) APPEND_MODIFY_LOGIC_CYBOI_FORMAT);
            } else {
                // The escape character ESC received before
                // is followed by another, second character
                // which is NOT an opening square bracket.
                // This is NOT going to be an ansi escape code sequence.
                // Unread this character so that it may be
                // processed once more later on.
                ungetwc(*c, f);
                // Set loop break flag.
                copy_integer(p2, (void*) TRUE_BOOLEAN_STATE_CYBOI_MODEL);
            }
        } else if (*c == *((wint_t*) ESCAPE_UNICODE_CHARACTER_CODE_MODEL)) {
            // The escape character ESC was received.
            // This might be the beginning of an ansi escape code.
            // Set escape character flag.
            copy_integer(p3, (void*) TRUE_BOOLEAN_STATE_CYBOI_MODEL);
            // Copy source character to destination character array.
            modify_item(p0, p5, (void*) WIDE_CHARACTER_TEXT_STATE_CYBOI_TYPE, (void*) FALSE_BOOLEAN_STATE_CYBOI_MODEL, (void*) PRIMITIVE_STATE_CYBOI_MODEL_COUNT, *NULL_POINTER_STATE_CYBOI_MODEL, (void*) VALUE_PRIMITIVE_STATE_CYBOI_NAME, (void*) TRUE_BOOLEAN_STATE_CYBOI_MODEL, (void*) APPEND_MODIFY_LOGIC_CYBOI_FORMAT);
        } else {
            // No special characters have been found.
            // So this is a normal source character
            // that is just copied to the destination.
            // Copy source character to destination character array.
            modify_item(p0, p5, (void*) WIDE_CHARACTER_TEXT_STATE_CYBOI_TYPE, (void*) FALSE_BOOLEAN_STATE_CYBOI_MODEL, (void*) PRIMITIVE_STATE_CYBOI_MODEL_COUNT, *NULL_POINTER_STATE_CYBOI_MODEL, (void*) VALUE_PRIMITIVE_STATE_CYBOI_NAME, (void*) TRUE_BOOLEAN_STATE_CYBOI_MODEL, (void*) APPEND_MODIFY_LOGIC_CYBOI_FORMAT);
            // CAUTION! Do NOT set loop break flag here.
            // More than just one character might have to be
            // received in a sequence, e.g. an ansi escape code.
            // In this case, only a WEOF will break the loop.
        }
    } else {
        // Set loop break flag.
        copy_integer(p2, (void*) TRUE_BOOLEAN_STATE_CYBOI_MODEL);
    }

Is the termios c_cc[VMIN] value possibly cached and not read from shared memory?

Find the project and all source code here: https://savannah.nongnu.org/svn/?group=cybop

Possibly related: Does making reference variable volatile, make all its field volatile too in java? "... objects are not variables. ... all threads see the same reference, not that they observe the same values for its inner content."

Christian Heller
  • 140
  • 2
  • 10
  • Is this on windows, linux or apple? There may be different solutions for every platform – cup Jun 29 '21 at 05:18
  • 2
    Why do you need non blocking if you use a thread for terminal input? The thread for terminal input can just block on read to get the next character. A finite state machine can decode the escape sequences. – fpiette Jun 29 '21 at 06:02
  • 1
    C volatile isn't the same as a Java volatile. A java volatile is a synchronization action and hence provides ordering/atomicity/visibility guarantees. The C volatile AFAIK only provides visibility guarantees (so compiler won't optimize-out the load/store). Can't you run C11 and use atomics? – pveentjer Jun 29 '21 at 06:24
  • *"Since I want to read ansi escape code sequences (three characters), the terminal must not block after each character ..."* -- You seem to misunderstand and misuse non-blocking mode. You are also using a stream rather than basic file I/O, so nonblocking I/O isn't really supported. Dynamically changing termios config is not advised. BTW finding the relevant code at that URL is impossible. – sawdust Jun 29 '21 at 19:47
  • I work on Debian GNU/Linux. – Christian Heller Jun 29 '21 at 21:56
  • Thanks to everybody for your ideas! I need some time to reflect about them. – Christian Heller Jun 29 '21 at 22:03
  • fpiette: The child thread is only to *detect* input, therefore the fgetwc is followed by ungetwc. Processing (reading, writing etc.) all happens in the main thread. So, the main thread is to detect ansi escape code sequences (such as the arrow-up key), but also standard characters. It must not block since otherwise, the whole program would hang, but other channels like xcb and socket are served as well. – Christian Heller Jun 29 '21 at 22:32
  • Where is it written that dynamically changing the termios config were not advised? Which stream do you mean? Do you have an example for an alternative? For me, the URL works fine. Just go to "Browse Sources Repository" and then "trunk". I give you some links in the comment below (due to the limited length of a comment). – Christian Heller Jun 29 '21 at 22:59
  • Thread creation: https://svn.savannah.gnu.org/viewvc/cybop/trunk/src/executor/activator/channel_enabler.c?revision=4275&view=markup https://svn.savannah.gnu.org/viewvc/cybop/trunk/src/executor/threader/spinner.c?revision=4275&view=markup – Christian Heller Jun 29 '21 at 23:00
  • Sensing: https://svn.savannah.gnu.org/viewvc/cybop/trunk/src/executor/sensor/unix_terminal/unix_terminal_sensor.c?revision=4275&view=markup https://svn.savannah.gnu.org/viewvc/cybop/trunk/src/executor/sensor/unix_terminal/message_unix_terminal_sensor.c?revision=4279&view=markup – Christian Heller Jun 29 '21 at 23:00
  • Reading: https://svn.savannah.gnu.org/viewvc/cybop/trunk/src/executor/streamer/reader/unix_terminal/character_unix_terminal_reader.c?revision=4275&view=markup https://svn.savannah.gnu.org/viewvc/cybop/trunk/src/executor/streamer/reader/unix_terminal/stream_unix_terminal_reader.c?revision=4275&view=markup – Christian Heller Jun 29 '21 at 23:00
  • 1
    *The child thread is only to detect...* This IMO is simply bad design. The child thread should read input, handling escape sequences and signal to the main thread when a *ready to use* input is available. You should probably create a queue with input items (characters, escape sequence) that the child thread fills and the main thread read. – fpiette Jun 30 '21 at 06:21
  • Ok, very good idea that I'd like to use. But how can the child thread signal the main thread by setting an integer flag? Once the child thread has read all valid characters, it is "hanging" in the blocking fgetwc function and canNOT set the integer flag anymore. Is there any solution? – Christian Heller Jun 30 '21 at 10:54
  • 1
    The thread can implement the queue as a pipe and simply write the input info to the pipe. The main thread can the wait on the pipe as it does wait on a socket. Probably the answers to [this SO question](https://stackoverflow.com/questions/5524780/i-want-to-wait-on-both-a-file-descriptor-and-a-mutex-whats-the-recommended-way) will help you. If it doesn't please open a new explicit question about the topic. – fpiette Jun 30 '21 at 12:05

0 Answers0