[libcamera-devel,1/3] test: process: Fix forking race

Message ID 20190910090418.30502-2-kieran.bingham@ideasonboard.com
State Accepted
Commit ee35abb7c7e0ba26d6453fb5e1c3b79e596c9fd0
Headers show
Series
  • test: process: Cleanup to process test
Related show

Commit Message

Kieran Bingham Sept. 10, 2019, 9:04 a.m. UTC
The procFinished event handler is registered after the process is
started, leading to the opportunity of a missed race.

Register the handler before the process is launched.

Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
---
 test/process/process_test.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

Comments

Laurent Pinchart Sept. 10, 2019, 9:40 a.m. UTC | #1
Hi Kieran,

Thank you for the patch.

On Tue, Sep 10, 2019 at 10:04:16AM +0100, Kieran Bingham wrote:
> The procFinished event handler is registered after the process is
> started, leading to the opportunity of a missed race.

That shouldn't be the case, as the procFinished signal is emitted from
the event loop. Still, connecting the signal before starting the process
is a good practice, so this patch is fine. You may want to mention in
the commit message that there's no actual race though.

Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

> Register the handler before the process is launched.
> 
> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> ---
>  test/process/process_test.cpp | 3 ++-
>  1 file changed, 2 insertions(+), 1 deletion(-)
> 
> diff --git a/test/process/process_test.cpp b/test/process/process_test.cpp
> index d264555e545f..701f156b5053 100644
> --- a/test/process/process_test.cpp
> +++ b/test/process/process_test.cpp
> @@ -47,12 +47,13 @@ protected:
>  		int exitCode = 42;
>  		vector<std::string> args;
>  		args.push_back(to_string(exitCode));
> +		proc_.finished.connect(this, &ProcessTest::procFinished);
> +
>  		int ret = proc_.start("/proc/self/exe", args);
>  		if (ret) {
>  			cerr << "failed to start process" << endl;
>  			return TestFail;
>  		}
> -		proc_.finished.connect(this, &ProcessTest::procFinished);
>  
>  		timeout.start(100);
>  		while (timeout.isRunning())
Kieran Bingham Sept. 10, 2019, 10:02 a.m. UTC | #2
Hi Laurent,

On 10/09/2019 10:40, Laurent Pinchart wrote:
> Hi Kieran,
> 
> Thank you for the patch.
> 
> On Tue, Sep 10, 2019 at 10:04:16AM +0100, Kieran Bingham wrote:
>> The procFinished event handler is registered after the process is
>> started, leading to the opportunity of a missed race.
> 
> That shouldn't be the case, as the procFinished signal is emitted from
> the event loop. Still, connecting the signal before starting the process
> is a good practice, so this patch is fine. You may want to mention in
> the commit message that there's no actual race though.

Aha, OK so I misunderstood the code.

Do the events get 'cached' and stored then ? If a slot is not connected,
can we accumulate an unlimited amount of events?

So if we connect a signal/slot at any arbitrary point in time later -
will we receive all of the historical calls? Or just the latest?


I've confirmed there is no race here by putting an arbitrary 5 second
sleep after calling .start(), and before connecting the signal and it
still works.



> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

Thanks, I'll update the commit message here as there is indeed no actual
race.

But it does make me feel a lot better to hook up the dependencies first :-)

> 
>> Register the handler before the process is launched.
>>
>> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
>> ---
>>  test/process/process_test.cpp | 3 ++-
>>  1 file changed, 2 insertions(+), 1 deletion(-)
>>
>> diff --git a/test/process/process_test.cpp b/test/process/process_test.cpp
>> index d264555e545f..701f156b5053 100644
>> --- a/test/process/process_test.cpp
>> +++ b/test/process/process_test.cpp
>> @@ -47,12 +47,13 @@ protected:
>>  		int exitCode = 42;
>>  		vector<std::string> args;
>>  		args.push_back(to_string(exitCode));
>> +		proc_.finished.connect(this, &ProcessTest::procFinished);
>> +
>>  		int ret = proc_.start("/proc/self/exe", args);
>>  		if (ret) {
>>  			cerr << "failed to start process" << endl;
>>  			return TestFail;
>>  		}
>> -		proc_.finished.connect(this, &ProcessTest::procFinished);
>>  
>>  		timeout.start(100);
>>  		while (timeout.isRunning())
>
Laurent Pinchart Sept. 10, 2019, 10:08 a.m. UTC | #3
Hi Kieran,

On Tue, Sep 10, 2019 at 11:02:56AM +0100, Kieran Bingham wrote:
> On 10/09/2019 10:40, Laurent Pinchart wrote:
> > On Tue, Sep 10, 2019 at 10:04:16AM +0100, Kieran Bingham wrote:
> >> The procFinished event handler is registered after the process is
> >> started, leading to the opportunity of a missed race.
> > 
> > That shouldn't be the case, as the procFinished signal is emitted from
> > the event loop. Still, connecting the signal before starting the process
> > is a good practice, so this patch is fine. You may want to mention in
> > the commit message that there's no actual race though.
> 
> Aha, OK so I misunderstood the code.
> 
> Do the events get 'cached' and stored then ? If a slot is not connected,
> can we accumulate an unlimited amount of events?

It's not about signals/slots, it's about listening to events. The
finished signal is emitted from Process::died(), called by
ProcessManager::sighandler(), itself a slot connected to the activated
signal of the sigEvent_ event notifier. The event notifier listens for
read events on the pipe used to communicate between the unix signal
handler (sigact() in process.cpp). Thus, when SIGCHLD is caught, we
write to the pipe, and the notification on the other side goes through
the event notifier, which is processed by the event loop.

> So if we connect a signal/slot at any arbitrary point in time later -
> will we receive all of the historical calls? Or just the latest?

Signals are not buffered, we keep no history.

> I've confirmed there is no race here by putting an arbitrary 5 second
> sleep after calling .start(), and before connecting the signal and it
> still works.
> 
> > Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> 
> Thanks, I'll update the commit message here as there is indeed no actual
> race.
> 
> But it does make me feel a lot better to hook up the dependencies first :-)
> 
> >> Register the handler before the process is launched.
> >>
> >> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> >> ---
> >>  test/process/process_test.cpp | 3 ++-
> >>  1 file changed, 2 insertions(+), 1 deletion(-)
> >>
> >> diff --git a/test/process/process_test.cpp b/test/process/process_test.cpp
> >> index d264555e545f..701f156b5053 100644
> >> --- a/test/process/process_test.cpp
> >> +++ b/test/process/process_test.cpp
> >> @@ -47,12 +47,13 @@ protected:
> >>  		int exitCode = 42;
> >>  		vector<std::string> args;
> >>  		args.push_back(to_string(exitCode));
> >> +		proc_.finished.connect(this, &ProcessTest::procFinished);
> >> +
> >>  		int ret = proc_.start("/proc/self/exe", args);
> >>  		if (ret) {
> >>  			cerr << "failed to start process" << endl;
> >>  			return TestFail;
> >>  		}
> >> -		proc_.finished.connect(this, &ProcessTest::procFinished);
> >>  
> >>  		timeout.start(100);
> >>  		while (timeout.isRunning())
Kieran Bingham Sept. 11, 2019, 8:19 a.m. UTC | #4
Hi Laurent,

On 10/09/2019 11:08, Laurent Pinchart wrote:
> Hi Kieran,
> 
> On Tue, Sep 10, 2019 at 11:02:56AM +0100, Kieran Bingham wrote:
>>
>>> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
>>
>> Thanks, I'll update the commit message here as there is indeed no actual
>> race.
>>
>> But it does make me feel a lot better to hook up the dependencies first :-)

Is this rewrite acceptable to you ?


test: process: Connect signal before launching process

The procFinished event handler is registered after the process is
started. This doesn't actually create any race, as the finished signal
is emitted after a SIGCHLD is caught and handled through the
ProcessManager and processed by the event loop.

To make it clear that resources are connected before the process runs,
register the handler before the process is started.



>>>> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
>>>> ---
>>>>  test/process/process_test.cpp | 3 ++-
>>>>  1 file changed, 2 insertions(+), 1 deletion(-)
>>>>
>>>> diff --git a/test/process/process_test.cpp b/test/process/process_test.cpp
>>>> index d264555e545f..701f156b5053 100644
>>>> --- a/test/process/process_test.cpp
>>>> +++ b/test/process/process_test.cpp
>>>> @@ -47,12 +47,13 @@ protected:
>>>>  		int exitCode = 42;
>>>>  		vector<std::string> args;
>>>>  		args.push_back(to_string(exitCode));
>>>> +		proc_.finished.connect(this, &ProcessTest::procFinished);
>>>> +
>>>>  		int ret = proc_.start("/proc/self/exe", args);
>>>>  		if (ret) {
>>>>  			cerr << "failed to start process" << endl;
>>>>  			return TestFail;
>>>>  		}
>>>> -		proc_.finished.connect(this, &ProcessTest::procFinished);
>>>>  
>>>>  		timeout.start(100);
>>>>  		while (timeout.isRunning())
>
Laurent Pinchart Sept. 12, 2019, 7:50 p.m. UTC | #5
Hi Kieran,

On Wed, Sep 11, 2019 at 09:19:16AM +0100, Kieran Bingham wrote:
> On 10/09/2019 11:08, Laurent Pinchart wrote:
> > On Tue, Sep 10, 2019 at 11:02:56AM +0100, Kieran Bingham wrote:
> >>
> >>> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> >>
> >> Thanks, I'll update the commit message here as there is indeed no actual
> >> race.
> >>
> >> But it does make me feel a lot better to hook up the dependencies first :-)
> 
> Is this rewrite acceptable to you ?
> 
> test: process: Connect signal before launching process
> 
> The procFinished event handler is registered after the process is
> started. This doesn't actually create any race, as the finished signal
> is emitted after a SIGCHLD is caught and handled through the
> ProcessManager and processed by the event loop.
> 
> To make it clear that resources are connected before the process runs,
> register the handler before the process is started.

I would say something along the lines of "However, to follow the best
practice that resources should be acquired before performing an action,
connect the finished signal before starting the process.".

Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

> >>>> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> >>>> ---
> >>>>  test/process/process_test.cpp | 3 ++-
> >>>>  1 file changed, 2 insertions(+), 1 deletion(-)
> >>>>
> >>>> diff --git a/test/process/process_test.cpp b/test/process/process_test.cpp
> >>>> index d264555e545f..701f156b5053 100644
> >>>> --- a/test/process/process_test.cpp
> >>>> +++ b/test/process/process_test.cpp
> >>>> @@ -47,12 +47,13 @@ protected:
> >>>>  		int exitCode = 42;
> >>>>  		vector<std::string> args;
> >>>>  		args.push_back(to_string(exitCode));
> >>>> +		proc_.finished.connect(this, &ProcessTest::procFinished);
> >>>> +
> >>>>  		int ret = proc_.start("/proc/self/exe", args);
> >>>>  		if (ret) {
> >>>>  			cerr << "failed to start process" << endl;
> >>>>  			return TestFail;
> >>>>  		}
> >>>> -		proc_.finished.connect(this, &ProcessTest::procFinished);
> >>>>  
> >>>>  		timeout.start(100);
> >>>>  		while (timeout.isRunning())

Patch

diff --git a/test/process/process_test.cpp b/test/process/process_test.cpp
index d264555e545f..701f156b5053 100644
--- a/test/process/process_test.cpp
+++ b/test/process/process_test.cpp
@@ -47,12 +47,13 @@  protected:
 		int exitCode = 42;
 		vector<std::string> args;
 		args.push_back(to_string(exitCode));
+		proc_.finished.connect(this, &ProcessTest::procFinished);
+
 		int ret = proc_.start("/proc/self/exe", args);
 		if (ret) {
 			cerr << "failed to start process" << endl;
 			return TestFail;
 		}
-		proc_.finished.connect(this, &ProcessTest::procFinished);
 
 		timeout.start(100);
 		while (timeout.isRunning())