Re: [PATCH] staging: mt7621-eth: Refactor RX ring resource allocation and cleanup

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Tue, May 29 2018, Dan Carpenter wrote:

> On Mon, May 21, 2018 at 09:37:31AM +1000, NeilBrown wrote:
>> On Fri, May 18 2018, Kamal Heib wrote:
>> 
>> > Simplify the code of allocate and cleanup RX ring resources by using
>> > helper functions, also make sure to free the allocated resources in
>> > cause of allocation failure.
>> >
>> > Signed-off-by: Kamal Heib <kamalheib1@xxxxxxxxx>
>> > ---
>> >  drivers/staging/mt7621-eth/mtk_eth_soc.c | 122 ++++++++++++++++++++-----------
>> >  1 file changed, 81 insertions(+), 41 deletions(-)
>> >
>> ....
>> >  static int mtk_dma_rx_alloc(struct mtk_eth *eth, struct mtk_rx_ring *ring)
>> >  {
>> > -	int i, pad = 0;
>> > +	int err;
>> >  
>> >  	ring->frag_size = mtk_max_frag_size(ETH_DATA_LEN);
>> >  	ring->rx_buf_size = mtk_max_buf_size(ring->frag_size);
>> > @@ -317,38 +366,23 @@ static int mtk_dma_rx_alloc(struct mtk_eth *eth, struct mtk_rx_ring *ring)
>> >  	ring->rx_data = kcalloc(ring->rx_ring_size, sizeof(*ring->rx_data),
>> >  				GFP_KERNEL);
>> >  	if (!ring->rx_data)
>> > -		goto no_rx_mem;
>> > +		return -ENOMEM;
>> 
>> Hi
>>  I haven't tested this patch yet (will try to in the next day or so) but
>>  it mostly looks good to me.
>>  I would rather see the above "return -ENOMEM" as a "goto free_rx_data".
>>  Having a single error-exit is generally more maintainable than having
>>  lots of separate 'return's, and kfree() is quite happy to be asked to
>>  kfree(NULL).
>>  In fact, all of the free function (even dma_free_coherent) cope
>>  ok will not having anything to free.  As devm_kzallo() and kcalloc()
>>  are used, all the pointers default to NULL which is safe. I would would
>>  prefer a single 'nomem' label which did:
>> 
>> nomem:
>>   dma_free_coherent(eth->dev, ring->rx_ring_size * sizeof(*ring->rx_dma),
>> 			  ring->rx_dma, ring->rx_phys);
>>   mtk_rx_free_frags(ring);
>>   kfree(ring->rx_data);
>>   return -ENOMEM;
>> 
>> 
>> But that is just my personal preference.
>
> You're sort of advocating two closely related styles of error handling.
> The first is a do-nothing error handling instead of direct returns.  The
> other style is a do-everything style error handling.

Yes, exactly.  And when there is nothing that needs doing, "everything"
and "nothing" are the same thing.

>
> Direct returns are preferable to do-nothing gotos because they are more
> readable.  "return -ENOMEM;" is more clear than "ret = -ENOMEM;
> goto free_rx_data;".  The second one seems clear but it doesn't free
> anything so the label name is actively misleading and confusing.  Do
> nothing gotos introduce "forgot to set the error code" bugs.

I don't like direct returns because they do not make modification easy.
If I find a need to allocate something else earlier, I need to go
and find all the direct returns and turn them into gotos.  I prefer to
make them gotos from the start.

And your examples are not good.  My preferred pattern is:

	ret = -ENOMEM;
        foo = alloc_foo();
        if (!foo)
        	goto abort;

You may only need to set ret once or twice.
I see nothing misleading or confusing there.

>
> The do-everything style error handling is the most bug prone style in
> the kernel.  So far this week I have seen 4 buggy one label patches vs
> 1 buggy multi label patch.

I cannot challenge your statistics, but I wonder if you might be
throwing the baby out with the bath water here.  Undoubtedly it is
possible to write do-everything error handling badly.  It is also
possible to write it well.  I'm not convinced it is fair blame the bad
code on the broad description "do everything error handling".

>
> I feel slightly bad picking on you but with your sample code which uses
> the nomem: label, you can't free dma resources which haven't been
> allocated.  I know you would have spotted it if you were writing a real
> patch instead of just throwing together sample code in your email
> client.  But the second issue is that you can't call
> mtk_rx_free_frags(ring); when "ring->rx_data" is NULL or it will Oops.
> That's subtle and so far as I know, static analysis would not warn about
> it.

"obviously" we would first fix mtk_rx_free_frags() to do the right thing
if ring->rx_data were NULL.  This is a crucial part of robust error
handling.
The very best error handling style is devm_*.  When that is not
practical, kfree() style, which quietly accepts NULL, is a good second
best.
Could it be that the various bugs you have seen could be blamed on
resource-release function which do not handle NULL well?  When these
bugs were fixed, were they fixed by fixing the resource-release
function (which would benefit many) or were they fixed by making the
code more robust against a poor interface by adding more checking?

>
> Multi label error handling is self documenting so you don't need to jump
> to the bottom of the function.  You only need to track the most recently
> allocated resource.
>
> 	foo = allocate_foo();
> 	if (!foo)
> 		return -ENOMEM;
>
> 	bar = allocate_bar();
> 	if (!bar) {
> 		ret = -ENOMEM;
> 		goto err_free_foo;
> 	}

I despise this structure because I cannot simply add

     baz = allocate_baz();
     if (!baz) {
     		ret = -ENOMEM;
                goto err_???;
     }

in the between these two.  I would need to also change the err_free_foo
to err_free_baz.
Possibly if the convention was "goto err_baz_failed" it might be
workable. Then the failure code would get

   free_baz(baz);
err_baz_failed:

added to it.  That would be less despicable, but not as good have
ensuring that every free_XXX() correctly handled NULL.

>
> This style of error handling is the most maintainable.  For example, I
> see bugs where people change a function from returning NULL to returning
> an error pointer but the error handling was relying on that kfree(NULL)
> is a no-op.  If you only free resources which have been allocated then
> you avoid that.

You've just made an excellent case for changing kfree() to something like

 if (unlikely(ZERO_OR_NULL_PTR(objp) || IS_ERR(objp)))
 	return;

I look forward do seeing your patch :-)

>
> One argument I have heard is that using single exit style code will
> prevent locking bugs where people do a direct return without dropping
> the lock.  I once looked at git history to find if this was true or not.
> My conclusion was that there are some people who add direct returns
> without thinking about locking at all.  Which ever style of error
> handling you use will not affect them.
>
> Sorry for the rant...  I should probably try to put together some
> figures to back this up...  But I think that if you look at the number
> of bugs, multi label error handling is going to be the clear winner.

No need to apologize, I enjoyed your rant - it gave me an excuse to rant
in return ;-)
Maybe more use of IS_ERR_OR_NULL() in resource release functions would
tip the scales concerning which pattern is the clear winner.

Thanks,
NeilBrown

>
> regards,
> dan carpenter

Attachment: signature.asc
Description: PGP signature

_______________________________________________
devel mailing list
devel@xxxxxxxxxxxxxxxxxxxxxx
http://driverdev.linuxdriverproject.org/mailman/listinfo/driverdev-devel

[Index of Archives]     [Linux Driver Backports]     [DMA Engine]     [Linux GPIO]     [Linux SPI]     [Video for Linux]     [Linux USB Devel]     [Linux Coverity]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]
  Powered by Linux