* [Qemu-devel] Error handling in realize() methods
@ 2015-12-08 13:47 Markus Armbruster
2015-12-08 14:19 ` Dr. David Alan Gilbert
0 siblings, 1 reply; 19+ messages in thread
From: Markus Armbruster @ 2015-12-08 13:47 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Peter Crosthwaite, Andreas Färber,
Peter Maydell
In general, code running withing a realize() method should not exit() on
error. Instad, errors should be propagated through the realize()
method. Additionally, the realize() method should fail cleanly,
i.e. carefully undo its side effects such as wiring of interrupts,
mapping of memory, and so forth. Tedious work, but necessary to make
hot plug safe.
Quite a few devices don't do that.
Some of them can be usefully hot-plugged, and for them unclean failures
are simply bugs. I'm going to mark the ones I can find.
Others are used only as onboard devices, and if their realize() fails,
the machine's init() will exit()[*]. In an ideal world, we'd start with
an empty board and cold-plugg devices, and there, clean failure may be
useful. In the world we live in, making these devices fail cleanly is a
lot of tedious work for no immediate gain.
Example: machine "kzm" and device "fsl,imx31". fsl_imx31_realize()
returns without cleanup on error. kzm_init() exit(1)s when realize
fails, so the lack of cleanup is a non-issue.
I think this is basically okay for now, but I'd like us to mark these
devices cannot_instantiate_with_device_add_yet, with /* Reason:
realize() method fails uncleanly */.
Opinions?
Next, let's consider the special case "out of memory".
Our general approach is to treat it as immediately fatal. This makes
sense, because when a smallish allocation fails, the process is almost
certainly doomed anyway. Moreover, memory allocation is frequent, and
attempting to recover from failed memory allocation adds loads of
hard-to-test error paths. These are *dangerous* unless carefully tested
(and we don't).
Certain important allocations we handle more gracefully. For instance,
we don't want to die when the user tries to hot-plug more memory than we
can allocate, or tries to open a QCOW2 image with a huge L1 table.
Guest memory allocation used to have the "immediately fatal" policy
baked in at a fairly low level, but it's since been lifted into callers;
see commit c261d77..fc7a580 and fixups 4f96676..0bdaa3a. During review
of the latter, Peter Crosthwaite called out the &error_fatal in the
realize methods and their supporting code. I agreed with him back then
that the errors should really be propagated. But now I've changed my
mind: I think we should treat these memory allocation failures like
we've always treated them, namely report and exit(1). Except for
"large" allocations, where we have a higher probability of failure, and
a more realistic chance to recover safely.
Can we agree that passing &error_fatal to memory_region_init_ram() &
friends is basically okay even in realize() methods and their supporting
code?
[*] Well, the ones that bother to check for errors, but that's a
separate problem.
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [Qemu-devel] Error handling in realize() methods
2015-12-08 13:47 [Qemu-devel] Error handling in realize() methods Markus Armbruster
@ 2015-12-08 14:19 ` Dr. David Alan Gilbert
2015-12-09 9:30 ` Markus Armbruster
0 siblings, 1 reply; 19+ messages in thread
From: Dr. David Alan Gilbert @ 2015-12-08 14:19 UTC (permalink / raw)
To: Markus Armbruster
Cc: Peter Maydell, Paolo Bonzini, Peter Crosthwaite, qemu-devel,
Andreas Färber
* Markus Armbruster (armbru@redhat.com) wrote:
> In general, code running withing a realize() method should not exit() on
> error. Instad, errors should be propagated through the realize()
> method. Additionally, the realize() method should fail cleanly,
> i.e. carefully undo its side effects such as wiring of interrupts,
> mapping of memory, and so forth. Tedious work, but necessary to make
> hot plug safe.
>
> Quite a few devices don't do that.
>
> Some of them can be usefully hot-plugged, and for them unclean failures
> are simply bugs. I'm going to mark the ones I can find.
>
> Others are used only as onboard devices, and if their realize() fails,
> the machine's init() will exit()[*]. In an ideal world, we'd start with
> an empty board and cold-plugg devices, and there, clean failure may be
> useful. In the world we live in, making these devices fail cleanly is a
> lot of tedious work for no immediate gain.
>
> Example: machine "kzm" and device "fsl,imx31". fsl_imx31_realize()
> returns without cleanup on error. kzm_init() exit(1)s when realize
> fails, so the lack of cleanup is a non-issue.
>
> I think this is basically okay for now, but I'd like us to mark these
> devices cannot_instantiate_with_device_add_yet, with /* Reason:
> realize() method fails uncleanly */.
>
> Opinions?
>
> Next, let's consider the special case "out of memory".
>
> Our general approach is to treat it as immediately fatal. This makes
> sense, because when a smallish allocation fails, the process is almost
> certainly doomed anyway. Moreover, memory allocation is frequent, and
> attempting to recover from failed memory allocation adds loads of
> hard-to-test error paths. These are *dangerous* unless carefully tested
> (and we don't).
>
> Certain important allocations we handle more gracefully. For instance,
> we don't want to die when the user tries to hot-plug more memory than we
> can allocate, or tries to open a QCOW2 image with a huge L1 table.
>
> Guest memory allocation used to have the "immediately fatal" policy
> baked in at a fairly low level, but it's since been lifted into callers;
> see commit c261d77..fc7a580 and fixups 4f96676..0bdaa3a. During review
> of the latter, Peter Crosthwaite called out the &error_fatal in the
> realize methods and their supporting code. I agreed with him back then
> that the errors should really be propagated. But now I've changed my
> mind: I think we should treat these memory allocation failures like
> we've always treated them, namely report and exit(1). Except for
> "large" allocations, where we have a higher probability of failure, and
> a more realistic chance to recover safely.
>
> Can we agree that passing &error_fatal to memory_region_init_ram() &
> friends is basically okay even in realize() methods and their supporting
> code?
I'd say it depends if they can be hotplugged; I think anything that we really
want to hotplug onto real running machines (as opposed to where we're just
hotplugging during setup) we should propagate errors properly.
And tbh I don't buy the small allocation argument; I think we should handle them
all; in my utopian world a guest wouldn't die unless there was no way out.
Dave
>
> [*] Well, the ones that bother to check for errors, but that's a
> separate problem.
>
--
Dr. David Alan Gilbert / dgilbert@redhat.com / Manchester, UK
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [Qemu-devel] Error handling in realize() methods
2015-12-08 14:19 ` Dr. David Alan Gilbert
@ 2015-12-09 9:30 ` Markus Armbruster
2015-12-09 10:29 ` Dr. David Alan Gilbert
2015-12-09 13:09 ` Paolo Bonzini
0 siblings, 2 replies; 19+ messages in thread
From: Markus Armbruster @ 2015-12-09 9:30 UTC (permalink / raw)
To: Dr. David Alan Gilbert
Cc: Peter Maydell, Peter Crosthwaite, qemu-devel, Andreas Färber,
Paolo Bonzini
"Dr. David Alan Gilbert" <dgilbert@redhat.com> writes:
> * Markus Armbruster (armbru@redhat.com) wrote:
>> In general, code running withing a realize() method should not exit() on
>> error. Instad, errors should be propagated through the realize()
>> method. Additionally, the realize() method should fail cleanly,
>> i.e. carefully undo its side effects such as wiring of interrupts,
>> mapping of memory, and so forth. Tedious work, but necessary to make
>> hot plug safe.
[...]
>> Next, let's consider the special case "out of memory".
>>
>> Our general approach is to treat it as immediately fatal. This makes
>> sense, because when a smallish allocation fails, the process is almost
>> certainly doomed anyway. Moreover, memory allocation is frequent, and
>> attempting to recover from failed memory allocation adds loads of
>> hard-to-test error paths. These are *dangerous* unless carefully tested
>> (and we don't).
>>
>> Certain important allocations we handle more gracefully. For instance,
>> we don't want to die when the user tries to hot-plug more memory than we
>> can allocate, or tries to open a QCOW2 image with a huge L1 table.
>>
>> Guest memory allocation used to have the "immediately fatal" policy
>> baked in at a fairly low level, but it's since been lifted into callers;
>> see commit c261d77..fc7a580 and fixups 4f96676..0bdaa3a. During review
>> of the latter, Peter Crosthwaite called out the &error_fatal in the
>> realize methods and their supporting code. I agreed with him back then
>> that the errors should really be propagated. But now I've changed my
>> mind: I think we should treat these memory allocation failures like
>> we've always treated them, namely report and exit(1). Except for
>> "large" allocations, where we have a higher probability of failure, and
>> a more realistic chance to recover safely.
>>
>> Can we agree that passing &error_fatal to memory_region_init_ram() &
>> friends is basically okay even in realize() methods and their supporting
>> code?
>
> I'd say it depends if they can be hotplugged; I think anything that we really
> want to hotplug onto real running machines (as opposed to where we're just
> hotplugging during setup) we should propagate errors properly.
>
> And tbh I don't buy the small allocation argument; I think we should handle them
> all; in my utopian world a guest wouldn't die unless there was no way out.
I guess in Utopia nobody ever makes stupid coding mistakes, the error
paths are all covered by a comprehensive test suite, and they make the
code prettier, too. Oh, and kids always eat their vegetables without
complaint.
However, we don't actually live in Utopia. In our world, error paths
clutter the code, are full of bugs, and the histogram of their execution
counts in testing (automated or not) has a frighteningly tall bar at
zero.
We're not going to make this problem less severe by making it bigger.
In fact, we consciously decided to hack off a big chunk with an axe:
commit 8a1d02aba9f986ca03d854184cd432ee98bcd179
Author: aliguori <aliguori@c046a42c-6fe2-441c-8c8c-71466251a162>
Date: Thu Feb 5 22:05:49 2009 +0000
Terminate emulation on memory allocation failure (Avi Kivity)
Memory allocation failures are a very rare condition on virtual-memory
hosts. They are also very difficult to handle correctly (especially in a
hardware emulation context). Because of this, it is better to gracefully
terminate emulation rather than executing untested or even unwritten recovery
code paths.
This patch changes the qemu memory allocation routines to terminate emulation
if an allocation failure is encountered.
Signed-off-by: Avi Kivity <avi@redhat.com>
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@6526 c046a42c-6fe2-441c-8c8c-71466251a162
Let me elaborate a bit on Avi's arguments:
* Memory allocations are very, very common. I count at least 2500,
Memory allocation failure is easily the most common *potential* error,
both statically and dynamically.
* Error recovery is always tedious and often hard. Especially when the
error happens in the middle of a complex operation that needs to be
fully rolled back to recover. A sensible approach is to acquire
resources early, when recovery is still relatively easy, but that's
often impractical for memory. This together with the ubiquitousness
of memory allocation makes memory allocation failure even harder to
handle than other kinds of errors.
* Not all allocations are equal. When an attempt to allocate a gigabyte
fails gracefully, there's a good chance that ordinary code can go on
allocating and freeing kilobytes as usual. But when an attempt to
allocate kilobytes fails, it's very likely that handling this failure
gracefully will only lead to another one, and another one, until some
buggy error handling puts the doomed process out of its misery.
Out of the ~2400 memory allocations that go through GLib, 59 can fail.
The others all terminate the process.
* How often do we see failures from these other 2300+? Bug reports from
users? As far as I can see, they're vanishingly rare.
* Reviewing and testing the error handling chains rooted at 59
allocations is hard enough, and I don't think we're doing particularly
well on the testing. What chance would we have with 2300+ more?
2300+ instances of a vanishingly rare error with generally complex
error handling and basically no test coverage: does that sound more
useful than 2300+ instances of a vanishingly rare error that kills the
process? If yes, how much of our limited resources is the difference
worth?
* You might argue that we don't have to handle all 2300+ instances, only
the ones reachable from hotplug. I think that's a pipe dream. Once
you permit "terminate on memory allocation failure" in general purpose
code, it hides behind innocent-looking function calls. Even if we
could find them all, we'd still have to add memory allocation failure
handling to lots of general purpose code just to make it usable for
hot plug. And then we'd get to keep finding them all forever.
I don't think handling all memory allocation failures gracefully
everywhere or even just within hotplug is practical this side of Utopia.
I believe all we could achieve trying is an illusion of graceful
handling that is sufficiently convincing to let us pat on our backs,
call the job done, and go back to working on stuff that matters to
actual users.
My current working assumption is that passing &error_fatal to
memory_region_init_ram() & friends is okay even in realize() methods and
their supporting code, except when the allocation can be large. Even
then, &error_fatal is better than buggy recovery code (which I can see
all over the place, but that's a separate topic).
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [Qemu-devel] Error handling in realize() methods
2015-12-09 9:30 ` Markus Armbruster
@ 2015-12-09 10:29 ` Dr. David Alan Gilbert
2015-12-09 11:10 ` Laszlo Ersek
` (2 more replies)
2015-12-09 13:09 ` Paolo Bonzini
1 sibling, 3 replies; 19+ messages in thread
From: Dr. David Alan Gilbert @ 2015-12-09 10:29 UTC (permalink / raw)
To: Markus Armbruster
Cc: Peter Maydell, Peter Crosthwaite, qemu-devel, Andreas Färber,
Paolo Bonzini
* Markus Armbruster (armbru@redhat.com) wrote:
> "Dr. David Alan Gilbert" <dgilbert@redhat.com> writes:
>
> > * Markus Armbruster (armbru@redhat.com) wrote:
> >> In general, code running withing a realize() method should not exit() on
> >> error. Instad, errors should be propagated through the realize()
> >> method. Additionally, the realize() method should fail cleanly,
> >> i.e. carefully undo its side effects such as wiring of interrupts,
> >> mapping of memory, and so forth. Tedious work, but necessary to make
> >> hot plug safe.
> [...]
> >> Next, let's consider the special case "out of memory".
> >>
> >> Our general approach is to treat it as immediately fatal. This makes
> >> sense, because when a smallish allocation fails, the process is almost
> >> certainly doomed anyway. Moreover, memory allocation is frequent, and
> >> attempting to recover from failed memory allocation adds loads of
> >> hard-to-test error paths. These are *dangerous* unless carefully tested
> >> (and we don't).
> >>
> >> Certain important allocations we handle more gracefully. For instance,
> >> we don't want to die when the user tries to hot-plug more memory than we
> >> can allocate, or tries to open a QCOW2 image with a huge L1 table.
> >>
> >> Guest memory allocation used to have the "immediately fatal" policy
> >> baked in at a fairly low level, but it's since been lifted into callers;
> >> see commit c261d77..fc7a580 and fixups 4f96676..0bdaa3a. During review
> >> of the latter, Peter Crosthwaite called out the &error_fatal in the
> >> realize methods and their supporting code. I agreed with him back then
> >> that the errors should really be propagated. But now I've changed my
> >> mind: I think we should treat these memory allocation failures like
> >> we've always treated them, namely report and exit(1). Except for
> >> "large" allocations, where we have a higher probability of failure, and
> >> a more realistic chance to recover safely.
> >>
> >> Can we agree that passing &error_fatal to memory_region_init_ram() &
> >> friends is basically okay even in realize() methods and their supporting
> >> code?
> >
> > I'd say it depends if they can be hotplugged; I think anything that we really
> > want to hotplug onto real running machines (as opposed to where we're just
> > hotplugging during setup) we should propagate errors properly.
> >
> > And tbh I don't buy the small allocation argument; I think we should handle them
> > all; in my utopian world a guest wouldn't die unless there was no way out.
>
> I guess in Utopia nobody ever makes stupid coding mistakes, the error
> paths are all covered by a comprehensive test suite, and they make the
> code prettier, too. Oh, and kids always eat their vegetables without
> complaint.
Yes, it's lovely.
> However, we don't actually live in Utopia. In our world, error paths
> clutter the code, are full of bugs, and the histogram of their execution
> counts in testing (automated or not) has a frighteningly tall bar at
> zero.
>
> We're not going to make this problem less severe by making it bigger.
> In fact, we consciously decided to hack off a big chunk with an axe:
>
> commit 8a1d02aba9f986ca03d854184cd432ee98bcd179
> Author: aliguori <aliguori@c046a42c-6fe2-441c-8c8c-71466251a162>
> Date: Thu Feb 5 22:05:49 2009 +0000
>
> Terminate emulation on memory allocation failure (Avi Kivity)
>
> Memory allocation failures are a very rare condition on virtual-memory
> hosts. They are also very difficult to handle correctly (especially in a
> hardware emulation context). Because of this, it is better to gracefully
> terminate emulation rather than executing untested or even unwritten recovery
> code paths.
>
> This patch changes the qemu memory allocation routines to terminate emulation
> if an allocation failure is encountered.
>
> Signed-off-by: Avi Kivity <avi@redhat.com>
> Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
>
>
> git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@6526 c046a42c-6fe2-441c-8c8c-71466251a162
>
> Let me elaborate a bit on Avi's arguments:
>
> * Memory allocations are very, very common. I count at least 2500,
> Memory allocation failure is easily the most common *potential* error,
> both statically and dynamically.
>
> * Error recovery is always tedious and often hard. Especially when the
> error happens in the middle of a complex operation that needs to be
> fully rolled back to recover. A sensible approach is to acquire
> resources early, when recovery is still relatively easy, but that's
> often impractical for memory. This together with the ubiquitousness
> of memory allocation makes memory allocation failure even harder to
> handle than other kinds of errors.
>
> * Not all allocations are equal. When an attempt to allocate a gigabyte
> fails gracefully, there's a good chance that ordinary code can go on
> allocating and freeing kilobytes as usual. But when an attempt to
> allocate kilobytes fails, it's very likely that handling this failure
> gracefully will only lead to another one, and another one, until some
> buggy error handling puts the doomed process out of its misery.
>
> Out of the ~2400 memory allocations that go through GLib, 59 can fail.
> The others all terminate the process.
>
> * How often do we see failures from these other 2300+? Bug reports from
> users? As far as I can see, they're vanishingly rare.
>
> * Reviewing and testing the error handling chains rooted at 59
> allocations is hard enough, and I don't think we're doing particularly
> well on the testing. What chance would we have with 2300+ more?
>
> 2300+ instances of a vanishingly rare error with generally complex
> error handling and basically no test coverage: does that sound more
> useful than 2300+ instances of a vanishingly rare error that kills the
> process? If yes, how much of our limited resources is the difference
> worth?
>
> * You might argue that we don't have to handle all 2300+ instances, only
> the ones reachable from hotplug. I think that's a pipe dream. Once
> you permit "terminate on memory allocation failure" in general purpose
> code, it hides behind innocent-looking function calls. Even if we
> could find them all, we'd still have to add memory allocation failure
> handling to lots of general purpose code just to make it usable for
> hot plug. And then we'd get to keep finding them all forever.
I didn't say it was easy :-)
> I don't think handling all memory allocation failures gracefully
> everywhere or even just within hotplug is practical this side of Utopia.
> I believe all we could achieve trying is an illusion of graceful
> handling that is sufficiently convincing to let us pat on our backs,
> call the job done, and go back to working on stuff that matters to
> actual users.
Handling them all probably isn't; handling some well defined cases is
probably possible.
Avi's argument is 6 years old, I suggest a few things have changed in that
time:
a) We now use the Error** mechanism in a lot of places - so a lot of
code already is supposed to deal with a function call failing; if a function
already has an error return and the caller deals with it, then making
the function deal with an allocation error and the caller handle it is
a lot easier.
b) The use of hotplug is now common - people really hate it when their
nice, happy working VM dies when they try and do something to it, like
hotplug or migrate.
c) I suspect (but don't know) that people are pushing the number of VMs on
a host harder than they used to, but there again memory got cheap.
I'm not that eager to protect every allocation; but in some common
cases, where we already have Error** paths and it's relatively simple,
then I think we should.
(OK, to be honest I think we should protect every allocation - but I do
have sympathy with the complexity/testing arguments).
Dave
> My current working assumption is that passing &error_fatal to
> memory_region_init_ram() & friends is okay even in realize() methods and
> their supporting code, except when the allocation can be large. Even
> then, &error_fatal is better than buggy recovery code (which I can see
> all over the place, but that's a separate topic).
--
Dr. David Alan Gilbert / dgilbert@redhat.com / Manchester, UK
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [Qemu-devel] Error handling in realize() methods
2015-12-09 10:29 ` Dr. David Alan Gilbert
@ 2015-12-09 11:10 ` Laszlo Ersek
2015-12-10 9:22 ` Markus Armbruster
2015-12-09 11:47 ` Peter Maydell
2015-12-10 9:27 ` Markus Armbruster
2 siblings, 1 reply; 19+ messages in thread
From: Laszlo Ersek @ 2015-12-09 11:10 UTC (permalink / raw)
To: Dr. David Alan Gilbert, Markus Armbruster
Cc: Peter Maydell, Peter Crosthwaite, Paolo Bonzini, qemu-devel,
Andreas Färber
On 12/09/15 11:29, Dr. David Alan Gilbert wrote:
> * Markus Armbruster (armbru@redhat.com) wrote:
>> "Dr. David Alan Gilbert" <dgilbert@redhat.com> writes:
>>
>>> * Markus Armbruster (armbru@redhat.com) wrote:
>>>> In general, code running withing a realize() method should not exit() on
>>>> error. Instad, errors should be propagated through the realize()
>>>> method. Additionally, the realize() method should fail cleanly,
>>>> i.e. carefully undo its side effects such as wiring of interrupts,
>>>> mapping of memory, and so forth. Tedious work, but necessary to make
>>>> hot plug safe.
>> [...]
>>>> Next, let's consider the special case "out of memory".
>>>>
>>>> Our general approach is to treat it as immediately fatal. This makes
>>>> sense, because when a smallish allocation fails, the process is almost
>>>> certainly doomed anyway. Moreover, memory allocation is frequent, and
>>>> attempting to recover from failed memory allocation adds loads of
>>>> hard-to-test error paths. These are *dangerous* unless carefully tested
>>>> (and we don't).
>>>>
>>>> Certain important allocations we handle more gracefully. For instance,
>>>> we don't want to die when the user tries to hot-plug more memory than we
>>>> can allocate, or tries to open a QCOW2 image with a huge L1 table.
>>>>
>>>> Guest memory allocation used to have the "immediately fatal" policy
>>>> baked in at a fairly low level, but it's since been lifted into callers;
>>>> see commit c261d77..fc7a580 and fixups 4f96676..0bdaa3a. During review
>>>> of the latter, Peter Crosthwaite called out the &error_fatal in the
>>>> realize methods and their supporting code. I agreed with him back then
>>>> that the errors should really be propagated. But now I've changed my
>>>> mind: I think we should treat these memory allocation failures like
>>>> we've always treated them, namely report and exit(1). Except for
>>>> "large" allocations, where we have a higher probability of failure, and
>>>> a more realistic chance to recover safely.
>>>>
>>>> Can we agree that passing &error_fatal to memory_region_init_ram() &
>>>> friends is basically okay even in realize() methods and their supporting
>>>> code?
>>>
>>> I'd say it depends if they can be hotplugged; I think anything that we really
>>> want to hotplug onto real running machines (as opposed to where we're just
>>> hotplugging during setup) we should propagate errors properly.
>>>
>>> And tbh I don't buy the small allocation argument; I think we should handle them
>>> all; in my utopian world a guest wouldn't die unless there was no way out.
>>
>> I guess in Utopia nobody ever makes stupid coding mistakes, the error
>> paths are all covered by a comprehensive test suite, and they make the
>> code prettier, too. Oh, and kids always eat their vegetables without
>> complaint.
>
> Yes, it's lovely.
>
>> However, we don't actually live in Utopia. In our world, error paths
>> clutter the code, are full of bugs, and the histogram of their execution
>> counts in testing (automated or not) has a frighteningly tall bar at
>> zero.
>>
>> We're not going to make this problem less severe by making it bigger.
>> In fact, we consciously decided to hack off a big chunk with an axe:
>>
>> commit 8a1d02aba9f986ca03d854184cd432ee98bcd179
>> Author: aliguori <aliguori@c046a42c-6fe2-441c-8c8c-71466251a162>
>> Date: Thu Feb 5 22:05:49 2009 +0000
>>
>> Terminate emulation on memory allocation failure (Avi Kivity)
>>
>> Memory allocation failures are a very rare condition on virtual-memory
>> hosts. They are also very difficult to handle correctly (especially in a
>> hardware emulation context). Because of this, it is better to gracefully
>> terminate emulation rather than executing untested or even unwritten recovery
>> code paths.
>>
>> This patch changes the qemu memory allocation routines to terminate emulation
>> if an allocation failure is encountered.
>>
>> Signed-off-by: Avi Kivity <avi@redhat.com>
>> Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
>>
>>
>> git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@6526 c046a42c-6fe2-441c-8c8c-71466251a162
>>
>> Let me elaborate a bit on Avi's arguments:
>>
>> * Memory allocations are very, very common. I count at least 2500,
>> Memory allocation failure is easily the most common *potential* error,
>> both statically and dynamically.
>>
>> * Error recovery is always tedious and often hard. Especially when the
>> error happens in the middle of a complex operation that needs to be
>> fully rolled back to recover. A sensible approach is to acquire
>> resources early, when recovery is still relatively easy, but that's
>> often impractical for memory. This together with the ubiquitousness
>> of memory allocation makes memory allocation failure even harder to
>> handle than other kinds of errors.
>>
>> * Not all allocations are equal. When an attempt to allocate a gigabyte
>> fails gracefully, there's a good chance that ordinary code can go on
>> allocating and freeing kilobytes as usual. But when an attempt to
>> allocate kilobytes fails, it's very likely that handling this failure
>> gracefully will only lead to another one, and another one, until some
>> buggy error handling puts the doomed process out of its misery.
>>
>> Out of the ~2400 memory allocations that go through GLib, 59 can fail.
>> The others all terminate the process.
>>
>> * How often do we see failures from these other 2300+? Bug reports from
>> users? As far as I can see, they're vanishingly rare.
>>
>> * Reviewing and testing the error handling chains rooted at 59
>> allocations is hard enough, and I don't think we're doing particularly
>> well on the testing. What chance would we have with 2300+ more?
>>
>> 2300+ instances of a vanishingly rare error with generally complex
>> error handling and basically no test coverage: does that sound more
>> useful than 2300+ instances of a vanishingly rare error that kills the
>> process? If yes, how much of our limited resources is the difference
>> worth?
>>
>> * You might argue that we don't have to handle all 2300+ instances, only
>> the ones reachable from hotplug. I think that's a pipe dream. Once
>> you permit "terminate on memory allocation failure" in general purpose
>> code, it hides behind innocent-looking function calls. Even if we
>> could find them all, we'd still have to add memory allocation failure
>> handling to lots of general purpose code just to make it usable for
>> hot plug. And then we'd get to keep finding them all forever.
>
> I didn't say it was easy :-)
>
>> I don't think handling all memory allocation failures gracefully
>> everywhere or even just within hotplug is practical this side of Utopia.
>> I believe all we could achieve trying is an illusion of graceful
>> handling that is sufficiently convincing to let us pat on our backs,
>> call the job done, and go back to working on stuff that matters to
>> actual users.
>
> Handling them all probably isn't; handling some well defined cases is
> probably possible.
>
> Avi's argument is 6 years old, I suggest a few things have changed in that
> time:
> a) We now use the Error** mechanism in a lot of places - so a lot of
> code already is supposed to deal with a function call failing; if a function
> already has an error return and the caller deals with it, then making
> the function deal with an allocation error and the caller handle it is
> a lot easier.
> b) The use of hotplug is now common - people really hate it when their
> nice, happy working VM dies when they try and do something to it, like
> hotplug or migrate.
> c) I suspect (but don't know) that people are pushing the number of VMs on
> a host harder than they used to, but there again memory got cheap.
>
> I'm not that eager to protect every allocation; but in some common
> cases, where we already have Error** paths and it's relatively simple,
> then I think we should.
>
> (OK, to be honest I think we should protect every allocation - but I do
> have sympathy with the complexity/testing arguments).
I've been following this discussion with great interest.
My opinion should not be considered, because I won't be turning my
opinion into new code, or an agreement to support / maintain code. :)
My opinion is that
- every single allocation needs to be checked rigorously,
- any partial construction of a more complex object needs to be rolled
back in precise reverse order upon encountering any kind of failure
(not just allocation)
- this should occur regardless of testing coverage (although projects
exist (for example, SQLite, IIRC) that use random or systematic
malloc() error injection in their test suite, for good coverage)
- the primary requirements for this to work are:
- a clear notion of ownership at any point in the code
- a disciplined approach to ownership tracking; for example, a helper
callee (responsible for constructing a member of a more complex
object) is forbidden from releasing "sibling" resources allocated
by the caller
This is possible to do (I'm doing it and enforcing it in OVMF), but it
takes a lot of discipline, and *historically* the QEMU codebase has
stunk, whenever I've looked at its ownership tracking during
construction of objects.
I feel that in the last sequence of months (years?) the developer
discipline and the codebase have improved a *great* deal. Still I cannot
say how feasible it would be to bring all existent code into conformance
with the above.
... As I said, I just wanted to express this opinion as another (not
really practical) data point. My children utterly hate spinach, so
Markus's counterpoint is definitely not lost on me.
Thanks
Laszlo
>
> Dave
>
>> My current working assumption is that passing &error_fatal to
>> memory_region_init_ram() & friends is okay even in realize() methods and
>> their supporting code, except when the allocation can be large. Even
>> then, &error_fatal is better than buggy recovery code (which I can see
>> all over the place, but that's a separate topic).
>
>
> --
> Dr. David Alan Gilbert / dgilbert@redhat.com / Manchester, UK
>
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [Qemu-devel] Error handling in realize() methods
2015-12-09 10:29 ` Dr. David Alan Gilbert
2015-12-09 11:10 ` Laszlo Ersek
@ 2015-12-09 11:47 ` Peter Maydell
2015-12-09 12:25 ` Laszlo Ersek
2015-12-09 13:21 ` Dr. David Alan Gilbert
2015-12-10 9:27 ` Markus Armbruster
2 siblings, 2 replies; 19+ messages in thread
From: Peter Maydell @ 2015-12-09 11:47 UTC (permalink / raw)
To: Dr. David Alan Gilbert
Cc: Paolo Bonzini, Peter Crosthwaite, Markus Armbruster,
Andreas Färber, QEMU Developers
On 9 December 2015 at 10:29, Dr. David Alan Gilbert <dgilbert@redhat.com> wrote:
> (OK, to be honest I think we should protect every allocation - but I do
> have sympathy with the complexity/testing arguments).
My view on this is that Linux overcommits, so the actual likely
way that "oops, out of memory" will manifest is by some page not
being able to be allocated-on-demand, at which point your process
is toast anyway. Checking malloc returns is really only checking
your virtual address space allocation, which typically speaking
always succeeds, except in the "we tried to get gigabytes at
once" case...
thanks
-- PMM
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [Qemu-devel] Error handling in realize() methods
2015-12-09 11:47 ` Peter Maydell
@ 2015-12-09 12:25 ` Laszlo Ersek
2015-12-09 13:21 ` Dr. David Alan Gilbert
1 sibling, 0 replies; 19+ messages in thread
From: Laszlo Ersek @ 2015-12-09 12:25 UTC (permalink / raw)
To: Peter Maydell, Dr. David Alan Gilbert
Cc: QEMU Developers, Paolo Bonzini, Peter Crosthwaite,
Markus Armbruster, Andreas Färber
On 12/09/15 12:47, Peter Maydell wrote:
> On 9 December 2015 at 10:29, Dr. David Alan Gilbert <dgilbert@redhat.com> wrote:
>> (OK, to be honest I think we should protect every allocation - but I do
>> have sympathy with the complexity/testing arguments).
>
> My view on this is that Linux overcommits, so the actual likely
> way that "oops, out of memory" will manifest is by some page not
> being able to be allocated-on-demand, at which point your process
> is toast anyway.
This is a frequent argument, but this behavior is not acceptable for
many production workloads (for example, it is not acceptable on my
laptop :)), and it can be disabled with the overcommit knobs under
/proc/sys/vm.
Also, the OOM killer destroying the "wrong" process is a recurrent
source of fun.
Of course, under memory pressure there is no telling which process will
get ENOMEM first, even with overcommit disabled, but that setting at
least allows the process to deal with the error, should it choose so.
On a heavy duty virtualization host, one can expect that *only* such
applications run that can "behave" in response to ENOMEM -- I believe
libvirtd is an example? --; hence disabling overcommit is especially valid.
(I continue to offer my (unsolicited) opinion in this thread, and I
continue encouraging QEMU people to ignore it whenever appropriate. I
don't strive to influence the decision; I'd just like to contribute to
the well-informed nature of the decision.)
Thanks
Laszlo
> Checking malloc returns is really only checking
> your virtual address space allocation, which typically speaking
> always succeeds, except in the "we tried to get gigabytes at
> once" case...
>
> thanks
> -- PMM
>
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [Qemu-devel] Error handling in realize() methods
2015-12-09 9:30 ` Markus Armbruster
2015-12-09 10:29 ` Dr. David Alan Gilbert
@ 2015-12-09 13:09 ` Paolo Bonzini
2015-12-09 13:12 ` Dr. David Alan Gilbert
2015-12-10 11:06 ` Markus Armbruster
1 sibling, 2 replies; 19+ messages in thread
From: Paolo Bonzini @ 2015-12-09 13:09 UTC (permalink / raw)
To: Markus Armbruster, Dr. David Alan Gilbert
Cc: Peter Maydell, Peter Crosthwaite, qemu-devel, Andreas Färber
On 09/12/2015 10:30, Markus Armbruster wrote:
> My current working assumption is that passing &error_fatal to
> memory_region_init_ram() & friends is okay even in realize() methods and
> their supporting code, except when the allocation can be large.
I suspect a lot of memory_region_init_ram()s could be considered
potentially large (at least in the 16-64 megabytes range). Propagation
of memory_region_init_ram() failures is easy enough, thanks to Error**,
that we should just do it.
Even if we don't, we should use &error_abort, not &error_fatal
(programmer error---due to laziness---rather than user error).
&error_fatal should really be restricted to code that is running very
close to main().
Paolo
> Even
> then, &error_fatal is better than buggy recovery code (which I can see
> all over the place, but that's a separate topic).
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [Qemu-devel] Error handling in realize() methods
2015-12-09 13:09 ` Paolo Bonzini
@ 2015-12-09 13:12 ` Dr. David Alan Gilbert
2015-12-09 13:43 ` Paolo Bonzini
2015-12-10 11:06 ` Markus Armbruster
1 sibling, 1 reply; 19+ messages in thread
From: Dr. David Alan Gilbert @ 2015-12-09 13:12 UTC (permalink / raw)
To: Paolo Bonzini
Cc: Peter Maydell, Peter Crosthwaite, Markus Armbruster,
Andreas Färber, qemu-devel
* Paolo Bonzini (pbonzini@redhat.com) wrote:
>
>
> On 09/12/2015 10:30, Markus Armbruster wrote:
> > My current working assumption is that passing &error_fatal to
> > memory_region_init_ram() & friends is okay even in realize() methods and
> > their supporting code, except when the allocation can be large.
>
> I suspect a lot of memory_region_init_ram()s could be considered
> potentially large (at least in the 16-64 megabytes range). Propagation
> of memory_region_init_ram() failures is easy enough, thanks to Error**,
> that we should just do it.
>
> Even if we don't, we should use &error_abort, not &error_fatal
> (programmer error---due to laziness---rather than user error).
> &error_fatal should really be restricted to code that is running very
> close to main().
No, we used to have error_abort and changed them out for error_fatal because
we were getting flooded with crash reports due to the aborts of people trying
to run VMs too big for their machine.
Dave
>
> Paolo
>
> > Even
> > then, &error_fatal is better than buggy recovery code (which I can see
> > all over the place, but that's a separate topic).
--
Dr. David Alan Gilbert / dgilbert@redhat.com / Manchester, UK
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [Qemu-devel] Error handling in realize() methods
2015-12-09 11:47 ` Peter Maydell
2015-12-09 12:25 ` Laszlo Ersek
@ 2015-12-09 13:21 ` Dr. David Alan Gilbert
1 sibling, 0 replies; 19+ messages in thread
From: Dr. David Alan Gilbert @ 2015-12-09 13:21 UTC (permalink / raw)
To: Peter Maydell
Cc: Paolo Bonzini, Peter Crosthwaite, Markus Armbruster,
Andreas Färber, QEMU Developers
* Peter Maydell (peter.maydell@linaro.org) wrote:
> On 9 December 2015 at 10:29, Dr. David Alan Gilbert <dgilbert@redhat.com> wrote:
> > (OK, to be honest I think we should protect every allocation - but I do
> > have sympathy with the complexity/testing arguments).
>
> My view on this is that Linux overcommits, so the actual likely
> way that "oops, out of memory" will manifest is by some page not
> being able to be allocated-on-demand, at which point your process
> is toast anyway. Checking malloc returns is really only checking
> your virtual address space allocation, which typically speaking
> always succeeds, except in the "we tried to get gigabytes at
> once" case...
We already get some failures, e.g. qemu-system-x86_64 -m 8192
fails with an allocation error on my laptop (8GB RAM+2GB swap).
I'm also not sure whether your statement is true once things like
cgroup/ulimit memory restrictions are used and/or mlock.
People really really hate OOM behaviour in production systems
and jump through hoops to try and avoid it.
Dave
>
> thanks
> -- PMM
--
Dr. David Alan Gilbert / dgilbert@redhat.com / Manchester, UK
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [Qemu-devel] Error handling in realize() methods
2015-12-09 13:12 ` Dr. David Alan Gilbert
@ 2015-12-09 13:43 ` Paolo Bonzini
0 siblings, 0 replies; 19+ messages in thread
From: Paolo Bonzini @ 2015-12-09 13:43 UTC (permalink / raw)
To: Dr. David Alan Gilbert
Cc: qemu-devel, Peter Maydell, Peter Crosthwaite, Markus Armbruster,
Andreas Färber
On 09/12/2015 14:12, Dr. David Alan Gilbert wrote:
>> > Even if we don't, we should use &error_abort, not &error_fatal
>> > (programmer error---due to laziness---rather than user error).
>> > &error_fatal should really be restricted to code that is running very
>> > close to main().
> No, we used to have error_abort and changed them out for error_fatal because
> we were getting flooded with crash reports due to the aborts of people trying
> to run VMs too big for their machine.
That's a different call site, it's memory_region_allocate_system_memory
and it currently does a manual error_report_err+exit(1). That one is
okay, because it's indeed running "very close to main()" (it's called by
machine_class->init, which is called by main). It could be kept
open-coded or changed to error_fatal.
Paolo
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [Qemu-devel] Error handling in realize() methods
2015-12-09 11:10 ` Laszlo Ersek
@ 2015-12-10 9:22 ` Markus Armbruster
2015-12-10 11:10 ` Laszlo Ersek
0 siblings, 1 reply; 19+ messages in thread
From: Markus Armbruster @ 2015-12-10 9:22 UTC (permalink / raw)
To: Laszlo Ersek
Cc: Peter Maydell, qemu-devel, Dr. David Alan Gilbert,
Peter Crosthwaite, Paolo Bonzini, Andreas Färber
Laszlo Ersek <lersek@redhat.com> writes:
> I've been following this discussion with great interest.
>
> My opinion should not be considered, because I won't be turning my
> opinion into new code, or an agreement to support / maintain code. :)
>
> My opinion is that
> - every single allocation needs to be checked rigorously,
> - any partial construction of a more complex object needs to be rolled
> back in precise reverse order upon encountering any kind of failure
> (not just allocation)
> - this should occur regardless of testing coverage (although projects
> exist (for example, SQLite, IIRC) that use random or systematic
> malloc() error injection in their test suite, for good coverage)
> - the primary requirements for this to work are:
> - a clear notion of ownership at any point in the code
> - a disciplined approach to ownership tracking; for example, a helper
> callee (responsible for constructing a member of a more complex
> object) is forbidden from releasing "sibling" resources allocated
> by the caller
>
> This is possible to do (I'm doing it and enforcing it in OVMF), but it
> takes a lot of discipline, and *historically* the QEMU codebase has
> stunk, whenever I've looked at its ownership tracking during
> construction of objects.
Doing it from the start is one thing. Laudable in theory, justifiable
in practice for some applications (I've seen it done for a satellite's
avionics software), but in general, life's too short for that kind of
manual error handling.
Retrofitting it into a big & messy existing code base is another thing
entirely. Believe me, I've done my share of cleaning up stuff. When
you're tempted to mess with something that works (although you barely
understand how or even why) to make it neater, or easier to maintain, or
handle vanishingly rare errors more gracefully, the one thing you need
to know is when *not* to do it, because the risk of you fscking it up
outweighs whatever you hope to gain.
> I feel that in the last sequence of months (years?) the developer
> discipline and the codebase have improved a *great* deal. Still I cannot
> say how feasible it would be to bring all existent code into conformance
> with the above.
Hah! We can't even get all the existent qdev init() methods converted
to realize(), which is a *much* simpler task. And that conveniently
ignores all the code that hasn't even made it to qdev.
We got bigger fish to fry. In fact, the queue of fish waiting to be
fried goes along the block a couple of times.
> ... As I said, I just wanted to express this opinion as another (not
> really practical) data point. My children utterly hate spinach, so
> Markus's counterpoint is definitely not lost on me.
To anyone advocating creating thousands (or even dozens) of non-trivial
new error paths: come back with a test suite. We have mountains of
evidence for chronic inability to get error paths right. In fact, I'm
working on a patch that adds a bunch of FIXMEs for one class of flawed
error recovery. Just FIXMEs, because I don't dare fixing the bugs
myself without a way to test the fix, and the fishes in the queue are
looking at me accusingly already.
If I may suggest a logo for such a test suite: kids eating spinach with
a smile feel apt.
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [Qemu-devel] Error handling in realize() methods
2015-12-09 10:29 ` Dr. David Alan Gilbert
2015-12-09 11:10 ` Laszlo Ersek
2015-12-09 11:47 ` Peter Maydell
@ 2015-12-10 9:27 ` Markus Armbruster
2 siblings, 0 replies; 19+ messages in thread
From: Markus Armbruster @ 2015-12-10 9:27 UTC (permalink / raw)
To: Dr. David Alan Gilbert
Cc: Peter Maydell, Peter Crosthwaite, Paolo Bonzini, qemu-devel,
Andreas Färber
"Dr. David Alan Gilbert" <dgilbert@redhat.com> writes:
> * Markus Armbruster (armbru@redhat.com) wrote:
>> "Dr. David Alan Gilbert" <dgilbert@redhat.com> writes:
>>
>> > * Markus Armbruster (armbru@redhat.com) wrote:
>> >> In general, code running withing a realize() method should not exit() on
>> >> error. Instad, errors should be propagated through the realize()
>> >> method. Additionally, the realize() method should fail cleanly,
>> >> i.e. carefully undo its side effects such as wiring of interrupts,
>> >> mapping of memory, and so forth. Tedious work, but necessary to make
>> >> hot plug safe.
>> [...]
>> >> Next, let's consider the special case "out of memory".
>> >>
>> >> Our general approach is to treat it as immediately fatal. This makes
>> >> sense, because when a smallish allocation fails, the process is almost
>> >> certainly doomed anyway. Moreover, memory allocation is frequent, and
>> >> attempting to recover from failed memory allocation adds loads of
>> >> hard-to-test error paths. These are *dangerous* unless carefully tested
>> >> (and we don't).
>> >>
>> >> Certain important allocations we handle more gracefully. For instance,
>> >> we don't want to die when the user tries to hot-plug more memory than we
>> >> can allocate, or tries to open a QCOW2 image with a huge L1 table.
>> >>
>> >> Guest memory allocation used to have the "immediately fatal" policy
>> >> baked in at a fairly low level, but it's since been lifted into callers;
>> >> see commit c261d77..fc7a580 and fixups 4f96676..0bdaa3a. During review
>> >> of the latter, Peter Crosthwaite called out the &error_fatal in the
>> >> realize methods and their supporting code. I agreed with him back then
>> >> that the errors should really be propagated. But now I've changed my
>> >> mind: I think we should treat these memory allocation failures like
>> >> we've always treated them, namely report and exit(1). Except for
>> >> "large" allocations, where we have a higher probability of failure, and
>> >> a more realistic chance to recover safely.
>> >>
>> >> Can we agree that passing &error_fatal to memory_region_init_ram() &
>> >> friends is basically okay even in realize() methods and their supporting
>> >> code?
>> >
>> > I'd say it depends if they can be hotplugged; I think anything
>> > that we really
>> > want to hotplug onto real running machines (as opposed to where we're just
>> > hotplugging during setup) we should propagate errors properly.
>> >
>> > And tbh I don't buy the small allocation argument; I think we
>> > should handle them
>> > all; in my utopian world a guest wouldn't die unless there was no way out.
>>
>> I guess in Utopia nobody ever makes stupid coding mistakes, the error
>> paths are all covered by a comprehensive test suite, and they make the
>> code prettier, too. Oh, and kids always eat their vegetables without
>> complaint.
>
> Yes, it's lovely.
>
>> However, we don't actually live in Utopia. In our world, error paths
>> clutter the code, are full of bugs, and the histogram of their execution
>> counts in testing (automated or not) has a frighteningly tall bar at
>> zero.
>>
>> We're not going to make this problem less severe by making it bigger.
>> In fact, we consciously decided to hack off a big chunk with an axe:
>>
>> commit 8a1d02aba9f986ca03d854184cd432ee98bcd179
>> Author: aliguori <aliguori@c046a42c-6fe2-441c-8c8c-71466251a162>
>> Date: Thu Feb 5 22:05:49 2009 +0000
>>
>> Terminate emulation on memory allocation failure (Avi Kivity)
>>
>> Memory allocation failures are a very rare condition on virtual-memory
>> hosts. They are also very difficult to handle correctly (especially in a
>> hardware emulation context). Because of this, it is better to gracefully
>> terminate emulation rather than executing untested or even unwritten recovery
>> code paths.
>>
>> This patch changes the qemu memory allocation routines to terminate emulation
>> if an allocation failure is encountered.
>>
>> Signed-off-by: Avi Kivity <avi@redhat.com>
>> Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
>>
>>
>> git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@6526 c046a42c-6fe2-441c-8c8c-71466251a162
>>
>> Let me elaborate a bit on Avi's arguments:
>>
>> * Memory allocations are very, very common. I count at least 2500,
>> Memory allocation failure is easily the most common *potential* error,
>> both statically and dynamically.
>>
>> * Error recovery is always tedious and often hard. Especially when the
>> error happens in the middle of a complex operation that needs to be
>> fully rolled back to recover. A sensible approach is to acquire
>> resources early, when recovery is still relatively easy, but that's
>> often impractical for memory. This together with the ubiquitousness
>> of memory allocation makes memory allocation failure even harder to
>> handle than other kinds of errors.
>>
>> * Not all allocations are equal. When an attempt to allocate a gigabyte
>> fails gracefully, there's a good chance that ordinary code can go on
>> allocating and freeing kilobytes as usual. But when an attempt to
>> allocate kilobytes fails, it's very likely that handling this failure
>> gracefully will only lead to another one, and another one, until some
>> buggy error handling puts the doomed process out of its misery.
>>
>> Out of the ~2400 memory allocations that go through GLib, 59 can fail.
>> The others all terminate the process.
>>
>> * How often do we see failures from these other 2300+? Bug reports from
>> users? As far as I can see, they're vanishingly rare.
>>
>> * Reviewing and testing the error handling chains rooted at 59
>> allocations is hard enough, and I don't think we're doing particularly
>> well on the testing. What chance would we have with 2300+ more?
>>
>> 2300+ instances of a vanishingly rare error with generally complex
>> error handling and basically no test coverage: does that sound more
>> useful than 2300+ instances of a vanishingly rare error that kills the
>> process? If yes, how much of our limited resources is the difference
>> worth?
>>
>> * You might argue that we don't have to handle all 2300+ instances, only
>> the ones reachable from hotplug. I think that's a pipe dream. Once
>> you permit "terminate on memory allocation failure" in general purpose
>> code, it hides behind innocent-looking function calls. Even if we
>> could find them all, we'd still have to add memory allocation failure
>> handling to lots of general purpose code just to make it usable for
>> hot plug. And then we'd get to keep finding them all forever.
>
> I didn't say it was easy :-)
>
>> I don't think handling all memory allocation failures gracefully
>> everywhere or even just within hotplug is practical this side of Utopia.
>> I believe all we could achieve trying is an illusion of graceful
>> handling that is sufficiently convincing to let us pat on our backs,
>> call the job done, and go back to working on stuff that matters to
>> actual users.
>
> Handling them all probably isn't; handling some well defined cases is
> probably possible.
>
> Avi's argument is 6 years old, I suggest a few things have changed in that
> time:
> a) We now use the Error** mechanism in a lot of places - so a lot of
> code already is supposed to deal with a function call failing; if a function
> already has an error return and the caller deals with it, then making
> the function deal with an allocation error and the caller handle it is
> a lot easier.
You still have to get the error path within the failing function right,
and tested.
> b) The use of hotplug is now common - people really hate it when their
> nice, happy working VM dies when they try and do something to it, like
> hotplug or migrate.
> c) I suspect (but don't know) that people are pushing the number of VMs on
> a host harder than they used to, but there again memory got cheap.
>
> I'm not that eager to protect every allocation; but in some common
> cases, where we already have Error** paths and it's relatively simple,
> then I think we should.
I won't argue we can't do more than 59.
I'm working on a patch adding FIXMEs to a few functions whose error
paths look relatively simple, but are in fact absolutely wrong.
> (OK, to be honest I think we should protect every allocation - but I do
> have sympathy with the complexity/testing arguments).
>
> Dave
>
>> My current working assumption is that passing &error_fatal to
>> memory_region_init_ram() & friends is okay even in realize() methods and
>> their supporting code, except when the allocation can be large. Even
>> then, &error_fatal is better than buggy recovery code (which I can see
>> all over the place, but that's a separate topic).
>
>
> --
> Dr. David Alan Gilbert / dgilbert@redhat.com / Manchester, UK
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [Qemu-devel] Error handling in realize() methods
2015-12-09 13:09 ` Paolo Bonzini
2015-12-09 13:12 ` Dr. David Alan Gilbert
@ 2015-12-10 11:06 ` Markus Armbruster
2015-12-10 11:21 ` Dr. David Alan Gilbert
2015-12-10 11:26 ` Paolo Bonzini
1 sibling, 2 replies; 19+ messages in thread
From: Markus Armbruster @ 2015-12-10 11:06 UTC (permalink / raw)
To: Paolo Bonzini
Cc: Peter Maydell, Peter Crosthwaite, Dr. David Alan Gilbert,
Andreas Färber, qemu-devel
Paolo Bonzini <pbonzini@redhat.com> writes:
> On 09/12/2015 10:30, Markus Armbruster wrote:
>> My current working assumption is that passing &error_fatal to
>> memory_region_init_ram() & friends is okay even in realize() methods and
>> their supporting code, except when the allocation can be large.
>
> I suspect a lot of memory_region_init_ram()s could be considered
> potentially large (at least in the 16-64 megabytes range). Propagation
> of memory_region_init_ram() failures is easy enough, thanks to Error**,
> that we should just do it.
Propagating an out-of-memory error right in realize() is easy. What's
not so easy is making realize() fail cleanly (all side effects undone;
we get that wrong in many places), and finding and propagating
out-of-memory errors hiding deeper in the call tree.
However, genuinely "large" allocations should be relatively few, and
handling them gracefully in hot-pluggable devices is probably feasible.
I doubt ensuring *all* allocations on behalf of a hot-pluggable device
are handled gracefully is a good use of our reseources, or even
feasible.
Likewise, graceful error handling for devices that cannot be hot-plugged
feels like a waste of resources we can ill afford. I think we should
simply document their non-gracefulness by either setting hotpluggable =
false or cannot_instantiate_with_device_add_yet = true with a suitable
comment.
> Even if we don't, we should use &error_abort, not &error_fatal
> (programmer error---due to laziness---rather than user error).
> &error_fatal should really be restricted to code that is running very
> close to main().
"Very close to main" is a question of dynamic context.
Consider a device that can only be created during machine initialization
(cannot_instantiate_with_device_add_yet = true or hotpluggable = false).
&error_fatal is perfectly adequate there. &error_abort would be
misleading, because when it fails, it's almost certainly because the
user tried to create too big a machine.
Now consider a hot-pluggable device. Its recognized "large" allocations
all fail gracefully. What about its other allocations? Two kinds: the
ones visible in the device model code, and the ones hiding elsewhere,
which include "a few" of the 2300+ uses of GLib memory allocation. The
latter exit(). Why should the former abort()?
Now use that hot-pluggable device during machine initialization.
abort() is again misleading.
Let's avoid a fruitless debate on when to exit() and when to abort() on
out-of-memory, and just stick to exit(). We don't need a core dump to
tell a developer to fix his lazy error handling.
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [Qemu-devel] Error handling in realize() methods
2015-12-10 9:22 ` Markus Armbruster
@ 2015-12-10 11:10 ` Laszlo Ersek
0 siblings, 0 replies; 19+ messages in thread
From: Laszlo Ersek @ 2015-12-10 11:10 UTC (permalink / raw)
To: Markus Armbruster
Cc: Peter Maydell, qemu-devel, Dr. David Alan Gilbert,
Peter Crosthwaite, Paolo Bonzini, Andreas Färber
On 12/10/15 10:22, Markus Armbruster wrote:
> Laszlo Ersek <lersek@redhat.com> writes:
>
>> I've been following this discussion with great interest.
>>
>> My opinion should not be considered, because I won't be turning my
>> opinion into new code, or an agreement to support / maintain code. :)
>>
>> My opinion is that
>> - every single allocation needs to be checked rigorously,
>> - any partial construction of a more complex object needs to be rolled
>> back in precise reverse order upon encountering any kind of failure
>> (not just allocation)
>> - this should occur regardless of testing coverage (although projects
>> exist (for example, SQLite, IIRC) that use random or systematic
>> malloc() error injection in their test suite, for good coverage)
>> - the primary requirements for this to work are:
>> - a clear notion of ownership at any point in the code
>> - a disciplined approach to ownership tracking; for example, a helper
>> callee (responsible for constructing a member of a more complex
>> object) is forbidden from releasing "sibling" resources allocated
>> by the caller
>>
>> This is possible to do (I'm doing it and enforcing it in OVMF), but it
>> takes a lot of discipline, and *historically* the QEMU codebase has
>> stunk, whenever I've looked at its ownership tracking during
>> construction of objects.
>
> Doing it from the start is one thing. Laudable in theory, justifiable
> in practice for some applications (I've seen it done for a satellite's
> avionics software), but in general, life's too short for that kind of
> manual error handling.
>
> Retrofitting it into a big & messy existing code base is another thing
> entirely. Believe me, I've done my share of cleaning up stuff. When
> you're tempted to mess with something that works (although you barely
> understand how or even why) to make it neater, or easier to maintain, or
> handle vanishingly rare errors more gracefully, the one thing you need
> to know is when *not* to do it, because the risk of you fscking it up
> outweighs whatever you hope to gain.
I agree. :)
Thanks
Laszlo
>
>> I feel that in the last sequence of months (years?) the developer
>> discipline and the codebase have improved a *great* deal. Still I cannot
>> say how feasible it would be to bring all existent code into conformance
>> with the above.
>
> Hah! We can't even get all the existent qdev init() methods converted
> to realize(), which is a *much* simpler task. And that conveniently
> ignores all the code that hasn't even made it to qdev.
>
> We got bigger fish to fry. In fact, the queue of fish waiting to be
> fried goes along the block a couple of times.
>
>> ... As I said, I just wanted to express this opinion as another (not
>> really practical) data point. My children utterly hate spinach, so
>> Markus's counterpoint is definitely not lost on me.
>
> To anyone advocating creating thousands (or even dozens) of non-trivial
> new error paths: come back with a test suite. We have mountains of
> evidence for chronic inability to get error paths right. In fact, I'm
> working on a patch that adds a bunch of FIXMEs for one class of flawed
> error recovery. Just FIXMEs, because I don't dare fixing the bugs
> myself without a way to test the fix, and the fishes in the queue are
> looking at me accusingly already.
>
> If I may suggest a logo for such a test suite: kids eating spinach with
> a smile feel apt.
>
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [Qemu-devel] Error handling in realize() methods
2015-12-10 11:06 ` Markus Armbruster
@ 2015-12-10 11:21 ` Dr. David Alan Gilbert
2015-12-10 11:22 ` Paolo Bonzini
2015-12-10 11:26 ` Paolo Bonzini
1 sibling, 1 reply; 19+ messages in thread
From: Dr. David Alan Gilbert @ 2015-12-10 11:21 UTC (permalink / raw)
To: Markus Armbruster
Cc: Paolo Bonzini, Peter Crosthwaite, qemu-devel, Andreas Färber,
Peter Maydell
* Markus Armbruster (armbru@redhat.com) wrote:
> Paolo Bonzini <pbonzini@redhat.com> writes:
>
> > On 09/12/2015 10:30, Markus Armbruster wrote:
> >> My current working assumption is that passing &error_fatal to
> >> memory_region_init_ram() & friends is okay even in realize() methods and
> >> their supporting code, except when the allocation can be large.
> >
> > I suspect a lot of memory_region_init_ram()s could be considered
> > potentially large (at least in the 16-64 megabytes range). Propagation
> > of memory_region_init_ram() failures is easy enough, thanks to Error**,
> > that we should just do it.
>
> Propagating an out-of-memory error right in realize() is easy. What's
> not so easy is making realize() fail cleanly (all side effects undone;
> we get that wrong in many places), and finding and propagating
> out-of-memory errors hiding deeper in the call tree.
>
> However, genuinely "large" allocations should be relatively few, and
> handling them gracefully in hot-pluggable devices is probably feasible.
>
> I doubt ensuring *all* allocations on behalf of a hot-pluggable device
> are handled gracefully is a good use of our reseources, or even
> feasible.
>
> Likewise, graceful error handling for devices that cannot be hot-plugged
> feels like a waste of resources we can ill afford. I think we should
> simply document their non-gracefulness by either setting hotpluggable =
> false or cannot_instantiate_with_device_add_yet = true with a suitable
> comment.
>
> > Even if we don't, we should use &error_abort, not &error_fatal
> > (programmer error---due to laziness---rather than user error).
> > &error_fatal should really be restricted to code that is running very
> > close to main().
>
> "Very close to main" is a question of dynamic context.
>
> Consider a device that can only be created during machine initialization
> (cannot_instantiate_with_device_add_yet = true or hotpluggable = false).
> &error_fatal is perfectly adequate there. &error_abort would be
> misleading, because when it fails, it's almost certainly because the
> user tried to create too big a machine.
>
> Now consider a hot-pluggable device. Its recognized "large" allocations
> all fail gracefully. What about its other allocations? Two kinds: the
> ones visible in the device model code, and the ones hiding elsewhere,
> which include "a few" of the 2300+ uses of GLib memory allocation. The
> latter exit(). Why should the former abort()?
>
> Now use that hot-pluggable device during machine initialization.
> abort() is again misleading.
>
> Let's avoid a fruitless debate on when to exit() and when to abort() on
> out-of-memory, and just stick to exit(). We don't need a core dump to
> tell a developer to fix his lazy error handling.
The tricky bit is when a user says 'it crashed with out of memory' -
and we just did an exit, how do we find out which bit we should
improve the error handling on? I guess the use of abort() could tell us
that - however it's a really big assumption that in an OOM case we'd
be able to dump the information.
Dave
--
Dr. David Alan Gilbert / dgilbert@redhat.com / Manchester, UK
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [Qemu-devel] Error handling in realize() methods
2015-12-10 11:21 ` Dr. David Alan Gilbert
@ 2015-12-10 11:22 ` Paolo Bonzini
0 siblings, 0 replies; 19+ messages in thread
From: Paolo Bonzini @ 2015-12-10 11:22 UTC (permalink / raw)
To: Dr. David Alan Gilbert, Markus Armbruster
Cc: Peter Maydell, Peter Crosthwaite, qemu-devel, Andreas Färber
On 10/12/2015 12:21, Dr. David Alan Gilbert wrote:
> I guess the use of abort() could tell us
> that - however it's a really big assumption that in an OOM case we'd
> be able to dump the information.
If it's not OOM, but just a multi-gigabyte allocation, we should.
Paolo
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [Qemu-devel] Error handling in realize() methods
2015-12-10 11:06 ` Markus Armbruster
2015-12-10 11:21 ` Dr. David Alan Gilbert
@ 2015-12-10 11:26 ` Paolo Bonzini
2015-12-10 12:25 ` Markus Armbruster
1 sibling, 1 reply; 19+ messages in thread
From: Paolo Bonzini @ 2015-12-10 11:26 UTC (permalink / raw)
To: Markus Armbruster
Cc: Peter Maydell, Peter Crosthwaite, Dr. David Alan Gilbert,
Andreas Färber, qemu-devel
On 10/12/2015 12:06, Markus Armbruster wrote:
> Paolo Bonzini <pbonzini@redhat.com> writes:
>
>> On 09/12/2015 10:30, Markus Armbruster wrote:
>>> My current working assumption is that passing &error_fatal to
>>> memory_region_init_ram() & friends is okay even in realize() methods and
>>> their supporting code, except when the allocation can be large.
>>
>> I suspect a lot of memory_region_init_ram()s could be considered
>> potentially large (at least in the 16-64 megabytes range). Propagation
>> of memory_region_init_ram() failures is easy enough, thanks to Error**,
>> that we should just do it.
>
> Propagating an out-of-memory error right in realize() is easy. What's
> not so easy is making realize() fail cleanly (all side effects undone;
> we get that wrong in many places), and finding and propagating
> out-of-memory errors hiding deeper in the call tree.
grep is your friend. We're talking of a subset of these:
hw/block/onenand.c: memory_region_init_ram(&s->ram, OBJECT(s), "onenand.ram",
hw/block/pflash_cfi01.c: memory_region_init_rom_device(
hw/block/pflash_cfi02.c: memory_region_init_rom_device(&pfl->orig_mem, OBJECT(pfl), pfl->be ?
hw/display/cg3.c: memory_region_init_ram(&s->rom, obj, "cg3.prom", FCODE_MAX_ROM_SIZE,
hw/display/cg3.c: memory_region_init_ram(&s->vram_mem, NULL, "cg3.vram", s->vram_size,
hw/display/g364fb.c: memory_region_init_ram_ptr(&s->mem_vram, NULL, "vram",
hw/display/qxl.c: memory_region_init_ram(&qxl->rom_bar, OBJECT(qxl), "qxl.vrom",
hw/display/qxl.c: memory_region_init_ram(&qxl->vram_bar, OBJECT(qxl), "qxl.vram",
hw/display/qxl.c: memory_region_init_ram(&qxl->vga.vram, OBJECT(dev), "qxl.vgavram",
hw/display/sm501.c: memory_region_init_ram(&s->local_mem_region, NULL, "sm501.local",
hw/display/tc6393xb.c: memory_region_init_ram(&s->vram, NULL, "tc6393xb.vram", 0x100000,
hw/display/tcx.c: memory_region_init_ram(&s->rom, obj, "tcx.prom", FCODE_MAX_ROM_SIZE,
hw/display/tcx.c: memory_region_init_ram(&s->vram_mem, OBJECT(s), "tcx.vram",
hw/display/vga.c: memory_region_init_ram(&s->vram, obj, "vga.vram", s->vram_size,
hw/display/vmware_vga.c: memory_region_init_ram(&s->fifo_ram, NULL, "vmsvga.fifo", s->fifo_size,
hw/dma/rc4030.c: memory_region_init_rom_device(&s->dma_tt, o,
hw/i386/pci-assign-load-rom.c: memory_region_init_ram(&dev->rom, owner, name, st.st_size, &error_abort);
hw/input/milkymist-softusb.c: memory_region_init_ram(&s->pmem, OBJECT(s), "milkymist-softusb.pmem",
hw/input/milkymist-softusb.c: memory_region_init_ram(&s->dmem, OBJECT(s), "milkymist-softusb.dmem",
hw/net/dp8393x.c: memory_region_init_ram(&s->prom, OBJECT(dev),
hw/net/milkymist-minimac2.c: memory_region_init_ram(&s->buffers, OBJECT(dev), "milkymist-minimac2.buffers",
hw/pci/pci.c: memory_region_init_ram(&pdev->rom, OBJECT(pdev), name, size, &error_fatal);
hw/s390x/sclp.c: memory_region_init_ram(standby_ram, NULL, id, this_subregion_size,
Some of them are in sysbus devices and can thus be ignored. Some of the
others may indeed need introduction of additional error propagation or may
be non-trivial.
> I doubt ensuring *all* allocations on behalf of a hot-pluggable device
> are handled gracefully is a good use of our reseources, or even
> feasible.
I agree. I don't think we have large allocations in device models,
other than from memory region allocation (e.g. VRAM).
> Consider a device that can only be created during machine initialization
> (cannot_instantiate_with_device_add_yet = true or hotpluggable = false).
> &error_fatal is perfectly adequate there. &error_abort would be
> misleading, because when it fails, it's almost certainly because the
> user tried to create too big a machine.
I agree here.
> Now consider a hot-pluggable device. Its recognized "large" allocations
> all fail gracefully. What about its other allocations? Two kinds: the
> ones visible in the device model code, and the ones hiding elsewhere,
> which include "a few" of the 2300+ uses of GLib memory allocation. The
> latter exit(). Why should the former abort()?
We've already ruled that the latter simply won't happen, so we can say at
the same time that g_malloc() does not exit at all. :)
&error_fatal is a user error, &error_abort is an assertion failure. This
is much closer to an assertion failure.
> Now use that hot-pluggable device during machine initialization.
> abort() is again misleading.
Perhaps, but---because it's hotpluggable---what happens during machine
initialization is much less important. It doesn't lead to lost data.
An abort() is much more likely to catch the developers' and the users'
attention and much more likely to be reported (compare "huh, it went
away?!?" with "abrt is asking me to open a Fedora bug, interesting").
> Let's avoid a fruitless debate on when to exit() and when to abort() on
> out-of-memory, and just stick to exit(). We don't need a core dump to
> tell a developer to fix his lazy error handling.
Perhaps we do...
Paolo
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [Qemu-devel] Error handling in realize() methods
2015-12-10 11:26 ` Paolo Bonzini
@ 2015-12-10 12:25 ` Markus Armbruster
0 siblings, 0 replies; 19+ messages in thread
From: Markus Armbruster @ 2015-12-10 12:25 UTC (permalink / raw)
To: Paolo Bonzini
Cc: qemu-devel, Peter Maydell, Peter Crosthwaite,
Dr. David Alan Gilbert, Andreas Färber
Paolo Bonzini <pbonzini@redhat.com> writes:
> On 10/12/2015 12:06, Markus Armbruster wrote:
>> Paolo Bonzini <pbonzini@redhat.com> writes:
>>
>>> On 09/12/2015 10:30, Markus Armbruster wrote:
>>>> My current working assumption is that passing &error_fatal to
>>>> memory_region_init_ram() & friends is okay even in realize() methods and
>>>> their supporting code, except when the allocation can be large.
>>>
>>> I suspect a lot of memory_region_init_ram()s could be considered
>>> potentially large (at least in the 16-64 megabytes range). Propagation
>>> of memory_region_init_ram() failures is easy enough, thanks to Error**,
>>> that we should just do it.
>>
>> Propagating an out-of-memory error right in realize() is easy. What's
>> not so easy is making realize() fail cleanly (all side effects undone;
>> we get that wrong in many places), and finding and propagating
>> out-of-memory errors hiding deeper in the call tree.
>
> grep is your friend. We're talking of a subset of these:
[...]
Yes, finding just the guest memory allocations isn't hard. But making
them fail cleanly is, judging from the code that gets it wrong.
[...]
^ permalink raw reply [flat|nested] 19+ messages in thread
end of thread, other threads:[~2015-12-10 12:25 UTC | newest]
Thread overview: 19+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2015-12-08 13:47 [Qemu-devel] Error handling in realize() methods Markus Armbruster
2015-12-08 14:19 ` Dr. David Alan Gilbert
2015-12-09 9:30 ` Markus Armbruster
2015-12-09 10:29 ` Dr. David Alan Gilbert
2015-12-09 11:10 ` Laszlo Ersek
2015-12-10 9:22 ` Markus Armbruster
2015-12-10 11:10 ` Laszlo Ersek
2015-12-09 11:47 ` Peter Maydell
2015-12-09 12:25 ` Laszlo Ersek
2015-12-09 13:21 ` Dr. David Alan Gilbert
2015-12-10 9:27 ` Markus Armbruster
2015-12-09 13:09 ` Paolo Bonzini
2015-12-09 13:12 ` Dr. David Alan Gilbert
2015-12-09 13:43 ` Paolo Bonzini
2015-12-10 11:06 ` Markus Armbruster
2015-12-10 11:21 ` Dr. David Alan Gilbert
2015-12-10 11:22 ` Paolo Bonzini
2015-12-10 11:26 ` Paolo Bonzini
2015-12-10 12:25 ` Markus Armbruster
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).