English Amiga Board


Go Back   English Amiga Board > Coders > Coders. Language > Coders. C/C++

 
 
Thread Tools
Old 01 June 2024, 22:22   #1
copse
Registered User
 
Join Date: Jul 2009
Location: Lala Land
Posts: 608
Ctrl+C handling in console application

This is for 68k with Workbench 3.1. I'd like to know why it does not work, rather than there are different ways to do it by upgrading.

Code:
    if (err = OpenDevice(SERIALNAME,0,(struct IORequest *)SerialIO,0L)) {
                if (err == 1)  
	          printf("Error: %s is in use\n", SERIALNAME);
                else
                    printf("Error: %s did not open (%d)\n",SERIALNAME, err);
        goto done;
    }
  1. I run a console application that opens the serial device successfully.
  2. I press Ctrl+C. AmigaDOS kills the task outright by itself.
  3. I run the console application again and it fails to open the serial device with "Error: serial.device is in use".

This is understandable. AmigaDOS doesn't clean up the open device and kills the application blindly. What I want to do is stop AmigaDOS from handling the signal and handle it myself and clean up and exit.

The following code does do that. I will consistently see "ctrl-c" and the device will be closed and the application will exit cleanly.

Code:
    LONG user_signal = SIGBREAKF_CTRL_C;
    serial_signal = 1L << serial_port->mp_SigBit;
    while (running) {
        SerialIO->IOSer.io_Command = SDCMD_QUERY;
        SendIO((struct IORequest *)SerialIO);

        signals = Wait(serial_signal | user_signal);
        if (signals & user_signal) {
            printf("ctrl-c\n");
            running = 0;
        }
But let's say I do the following:

Code:
    timer_signal = 1L << timer_port->mp_SigBit;
    while (running) {
        time_request->tr_node.io_Command = TR_ADDREQUEST;
        time_request->tr_time.tv_secs = 20;
        time_request->tr_time.tv_micro = 500000L;
        SendIO((struct IORequest *)time_request);
        signals = Wait(timer_signal | user_signal);
        if (signals & user_signal) {
            printf("ctrl-c\n");
            running = 0;
        }
Now this in theory should wait until 20 seconds has passed or Ctrl-c is pressed. What happens is it loops with 20 seconds of blocking. If I press Ctrl-c AmigaDOS kills the application, and I never see "ctrl-c" output.

I want to understand why this code does not work. Is there a way of having the Ctrl-c interrupting the Wait and not being processed by AmigaDOS?
copse is offline  
Old 01 June 2024, 22:33   #2
copse
Registered User
 
Join Date: Jul 2009
Location: Lala Land
Posts: 608
Here's the full source code for reference. It is in the state where it works, exits with Ctrl-C and reads commands from the serial port.

https://github.com/rmtew/amiga-exper...sync_read_31.c
copse is offline  
Old 02 June 2024, 14:19   #3
hooverphonique
ex. demoscener "Bigmama"
 
Join Date: Jun 2012
Location: Fyn / Denmark
Posts: 1,643
is user_signal correctly initialized in the timer example?
hooverphonique is offline  
Old 02 June 2024, 15:03   #4
thomas
Registered User
 
thomas's Avatar
 
Join Date: Jan 2002
Location: Germany
Posts: 7,028
Quote:
Originally Posted by copse View Post
If I press Ctrl-c AmigaDOS kills the application
That's not true. AmigaDOS never kills an application.

It is your compiler's runtime which kills your program.

You either have to disable Ctrl-C handling on the runtime (that's compiler specific, so I cannot tell you how to do it) or avoid using ANSI C functions. For example you could use Printf instead of printf.
thomas is offline  
Old 02 June 2024, 15:49   #5
Minuous
Coder/webmaster/gamer
 
Minuous's Avatar
 
Join Date: Oct 2001
Location: Canberra/Australia
Posts: 2,678
For SAS/C, you just add these lines, as shown in the RKMs:

int CXBRK(void) { return 0; } /* Disable SAS/Lattice Ctrl-C handling */
void chkabort(void) { return; } /* really */
Minuous is offline  
Old 02 June 2024, 22:06   #6
copse
Registered User
 
Join Date: Jul 2009
Location: Lala Land
Posts: 608
Thanks guys. That's points me in the right direction. I now need to work out how vbcc does this - which is in the latest online documentation.

Question 2: how do people conventionally clean up things like the open serial device in the event of control-c and other unforeseen circumstances? Is it to override and catch those signals, like this?

Last edited by copse; 02 June 2024 at 22:17.
copse is offline  
Old 02 June 2024, 22:09   #7
copse
Registered User
 
Join Date: Jul 2009
Location: Lala Land
Posts: 608
Quote:
Originally Posted by hooverphonique View Post
is user_signal correctly initialized in the timer example?
Well, this is kind of a chicken and egg situation. If I knew what correctly initialized meant, I should in theory be doing it. But if I don't know what it means I do not know if I am doing it

All my code is constructed of examples in online autodoc looking web pages and such things as far as I can tell. Some of which contradicts each other - like one says clean up your IO and another never mentions it. All the message and IO handling is a black box to me. I can understand what DoIO and SendIO do, but what messages I should expect and what IO calls I need to do post results or changed circumstances is opaque.
copse is offline  
Old 02 June 2024, 22:22   #8
copse
Registered User
 
Join Date: Jul 2009
Location: Lala Land
Posts: 608
Question 3: My sense is that SendIO for the serial query is pointless. That perhaps what I have to do is:
  1. DoIO a QUERY on the serial device and handle it.
  2. SendIO on the timer device for a reasonable yield time.
  3. Wait on the timer signal and the Ctrl-c signal.
  4. If Ctrl-c is pressed exit cleanly
  5. Go to 1.

What I want to do is:
  1. SendIO on the timer sleep time, Ctrl-C and serial.
  2. Wait until actual buffered serial data OR Ctrl-C or timer yield over.
  3. If Ctrl-c exit cleanly.
  4. If serial data process.
  5. Go to 1.

At this time I do not know how to Wait until serial data present.
copse is offline  
Old 02 June 2024, 22:42   #9
Thomas Richter
Registered User
 
Join Date: Jan 2019
Location: Germany
Posts: 3,315
Quote:
Originally Posted by copse View Post
At this time I do not know how to Wait until serial data present.
Many possibilties. Your serial and timer IORequest should either have two separate ports or a single port. The latter is sufficient. You fire off the timerequest by SendIO, and the serial request by SendIO. Then you do a Wait() with the signal mask taking the signal from the reply port of the IORequest(s), and SIGBREAK_CTRL_C. You check the returned bitmask. If it contains SIGBREAK_CTRL_C, you use AbortIO() on both the timer and the serial IO, then WaitIO() on each of the requests to receive the request back, then close the devices and abort the program.

If the returned signal mask is that of the reply port, you use CheckIO() on the serial device IORequest. If CheckIO() indicates that the serial request is ready, you call AbortIO() on the timerequest, then WaitIO() on both requests, and handle whatever data you received.

If CheckIO() indicates that the timerequest is ready, you call AbortIO() on the serial request (if you want to?), then WaitIO() on both of them, and then handle whatever needs to be handled on a timeout.

Thus, an IORequest can have three states: In your possession, free. (F), queued in the device for processing (D), and ready and processed in your port (R).

F -> D is by SendIO(), and you should *only* call SendIO() while a request is in state F ("free") as otherwise you are causing a lot of confusion.

D-> R is by the device, and this state transition triggers a signal you can wait on. For serial: When data was received (or send out), for the timer, when the timespan elapsed.

D-> R is also (forcefully) possible by AbortIO() saying "I need the request back right now, do not process it anymore".

R-> F is done by WaitIO(), which also waits for the request to be in state R.

CheckIO() can distinguish between states D (not ready) and R (ready). It should not be called in state F (free).

HTHH,

Thomas
Thomas Richter is offline  
Old 02 June 2024, 22:54   #10
copse
Registered User
 
Join Date: Jul 2009
Location: Lala Land
Posts: 608
Thanks Thomas. That breakdown is very handy.

I've also realised I am not thinking good following your hint in that breakdown, as I can of course block on a read of 1 character and wait on that to avoid the polling loop of the wait on the query. In fact this is documented if I search for CMD_READ for the serial device.

Thanks again!
copse is offline  
Old 03 June 2024, 04:58   #11
copse
Registered User
 
Join Date: Jul 2009
Location: Lala Land
Posts: 608
In the case where the Wait returns from either Ctrl-c or serial, I call AbortIO/WaitIO on the timer. At this point the timer signal appears to be set. Is this expected? Should I clear it with SetSignal(0L, timer_signal)? Is there a standard pattern done here?
copse is offline  
Old 03 June 2024, 07:08   #12
Thomas Richter
Registered User
 
Join Date: Jan 2019
Location: Germany
Posts: 3,315
The pattern is to keep the signal bit set, but rather depend on CheckIO() when you received signals whether a request submitted earlier did actually return. A Wait() on a signal set returns immediately, but if you run into CheckIO() later and find that your request is still busy, your program will just loop for another round, which is harmless. If you set the signal to 0 without knowing whether the request is still busy, you could miss a request that just returned, and the next Wait() will wait forever. IOWs, the only thing that should clear signals is Wait(), and not SetSignal().
Thomas Richter is offline  
Old 03 June 2024, 07:45   #13
copse
Registered User
 
Join Date: Jul 2009
Location: Lala Land
Posts: 608
What might I be missing? This infinite loops and never clears the flag. If I remove the 'if (CheckIO..' line, same result.

Code:
while (running) {
    ULONG signals2 = SetSignals(0L, 0L);
    if ((signals2 & time_signal) != 0)
        printf("time signal still set.\n");
    time_request->tr_node.io_Command = TR_ADDREQUEST;
    time_request->tr_time.tv_secs = 5;
    SendIO(time_request);
    signals = WaitIO(serial_and_timer_and_ctrlc);
    if (CheckIO(time_request) == NULL)
        AbortIO(time_request); /* Ensure pending is complete/killed. */
    WaitIO(time_request);
    ...
}
copse is offline  
Old 03 June 2024, 08:22   #14
thomas
Registered User
 
thomas's Avatar
 
Join Date: Jan 2002
Location: Germany
Posts: 7,028
This looks so very complicated.

Hers is a more simple to understand code snippet:
Code:
start_timer (timer);
start_serial_read(serial,buffer);

while (running) {
	signals_received = Wait(serial_signal | timer_signal | SIGBREAKF_CTRL_C);

	if (signals_received & seriaL_signal) {
		WaitIO(serial);
		process_serial_buffer(buffer);
		start_serial_read(serial,buffer);
	}

	if (signals_received & timer_signal) {
		WaitIO(timer);
		printf("timeout\n");
		running = FALSE;
	}

	if (signals_received & SIGBREAKF_CTRL_C)
		printf("Ctrl-C\n");
		running = FALSE;
	}
}

if (CheckIO(serial) == NULL) {
	AbortIO (serial);
	WaitIO (serial);
	}

if (CheckIO(timer) == NULL) {
	AbortIO (timer);
	WaitIO (timer);
	}
Note that I intentionally do not share the message port. It would make things overcomplicated. You can share message ports when you run out of signals or if your app can open an infinite number of devices/windows/whatever. But in this simple use case it is much easier to have a seperate port+signal for each component.
thomas is offline  
Old 03 June 2024, 18:38   #15
Thomas Richter
Registered User
 
Join Date: Jan 2019
Location: Germany
Posts: 3,315
Quote:
Originally Posted by thomas View Post
[/code]Note that I intentionally do not share the message port. It would make things overcomplicated. You can share message ports when you run out of signals or if your app can open an infinite number of devices/windows/whatever. But in this simple use case it is much easier to have a seperate port+signal for each component.
Depending on what should actually time out on a timeout, you would probably need to AbortIO() the serial request there. Actually, I do not see why sharing a message would make things more complicated? There is one signal less to wait on, and you still have all the information you need at your fingertips with CheckIO()? Anyhow, it's not important here.
Thomas Richter is offline  
Old 03 June 2024, 20:47   #16
aros-sg
Registered User
 
Join Date: Nov 2015
Location: Italy
Posts: 192
Quote:
Originally Posted by thomas View Post
This looks so very complicated.Hers is a more simple to understand code snippet:
This IO stuff is tricky and complicated and easy to get wrong. In your simpler code example at the end outside the running loop your cleanup code (checkio, abortio, waitio) may look perfect at first look, but (even if in many cases this may not be a problem) there are cases where your example can end up with iorequests which do not get removed from replyport. Like if the timer and/or the serial request complete just while code is in "running" loop handling the SIGBREAKF_CTRL_C which the user just pressed. The loop exits and the cleanup code never calls WaitIO, because the request(s) are already completed.Having some "timerio_sent" and "serialio_sent" (set to YES before SendIO(), set to NO after WaitIO() or manual removing from replyport) variable may help, as CheckIO() is not enough to know if you have to do some cleanup.
aros-sg is offline  
Old 03 June 2024, 21:46   #17
copse
Registered User
 
Join Date: Jul 2009
Location: Lala Land
Posts: 608
Quote:
Originally Posted by thomas View Post
This looks so very complicated.

Hers is a more simple to understand code snippet:
This is simpler because it is different. The requirement was that I should be able to restart the timer every loop. What stops me from doing this is the leaked signal.

Please look at it as helping me understand how it works, why it does not work and what the proper way to do what is actually being done is where it would work.

Why is the WaitIO not cleaning up the signal seems to be the question as far as I can tell. The fact there's disagreement about how to even do your simpler case right is.. umm.. making me wonder who actually understood this stuff back in the day
copse is offline  
Old 09 June 2024, 13:30   #18
phx
Natteravn
 
phx's Avatar
 
Join Date: Nov 2009
Location: Herford / Germany
Posts: 2,546
Quote:
Originally Posted by copse View Post
Thanks guys. That's points me in the right direction. I now need to work out how vbcc does this
Code:
void _chkabort(void)
{
}
phx is offline  
 


Currently Active Users Viewing This Thread: 1 (0 members and 1 guests)
 
Thread Tools

Similar Threads
Thread Thread Starter Forum Replies Last Post
Where did I see that non-recursive directory handling c-example? hceline Coders. C/C++ 9 17 November 2023 23:25
[blitz] - palette handling - need some help peceha Coders. Blitz Basic 2 18 March 2021 18:49
Take control of Alt + TAB, Ctrl + Alt + Del and Ctrl + ESC. Ponki1986 support.WinUAE 6 13 December 2020 07:54
File Handling in BLitzmode timeslip1974 Coders. Blitz Basic 4 10 September 2019 17:29
Cookie handling alexh project.EAB 1 22 October 2007 21:52

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT +2. The time now is 04:24.

Top

Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2024, vBulletin Solutions Inc.
Page generated in 0.09162 seconds with 13 queries