diff mbox

[API-NEXT,2/2] packet_io: release unsent packets after odp_pktio_send()

Message ID 1433349859-28565-2-git-send-email-zoltan.kiss@linaro.org
State New
Headers show

Commit Message

Zoltan Kiss June 3, 2015, 4:44 p.m. UTC
And change the behaviour in linux-generic implementation, where it's releasing
it during the call.

Signed-off-by: Zoltan Kiss <zoltan.kiss@linaro.org>
---
 example/packet/odp_pktio.c                 |  9 ++++++++-
 platform/linux-generic/odp_packet_io.c     | 10 +++++++++-
 platform/linux-generic/odp_packet_socket.c |  6 ------
 test/performance/odp_l2fwd.c               | 11 +++++++++--
 4 files changed, 26 insertions(+), 10 deletions(-)

Comments

Stuart Haslam June 3, 2015, 5 p.m. UTC | #1
On Wed, Jun 03, 2015 at 05:44:19PM +0100, Zoltan Kiss wrote:
> And change the behaviour in linux-generic implementation, where it's releasing
> it during the call.
> 
> Signed-off-by: Zoltan Kiss <zoltan.kiss@linaro.org>
> ---
>  example/packet/odp_pktio.c                 |  9 ++++++++-
>  platform/linux-generic/odp_packet_io.c     | 10 +++++++++-
>  platform/linux-generic/odp_packet_socket.c |  6 ------
>  test/performance/odp_l2fwd.c               | 11 +++++++++--
>  4 files changed, 26 insertions(+), 10 deletions(-)
> 
> diff --git a/example/packet/odp_pktio.c b/example/packet/odp_pktio.c
> index f08d9f4..4242caa 100644
> --- a/example/packet/odp_pktio.c
> +++ b/example/packet/odp_pktio.c
> @@ -282,9 +282,16 @@ static void *pktio_ifburst_thread(void *arg)
>  			/* Drop packets with errors */
>  			pkts_ok = drop_err_pkts(pkt_tbl, pkts);
>  			if (pkts_ok > 0) {
> +				int sent;
>  				/* Swap Eth MACs and IP-addrs */
>  				swap_pkt_addrs(pkt_tbl, pkts_ok);
> -				odp_pktio_send(pktio, pkt_tbl, pkts_ok);
> +				sent = odp_pktio_send(pktio, pkt_tbl, pkts_ok);
> +				if (odp_unlikely(sent < pkts_ok)) {
> +					err_cnt += pkts_ok - sent;
> +					do
> +						odp_packet_free(pkt_tbl[sent++]);
> +					while (++sent < pkts_ok);

You're incrementing sent twice.

> +				}
>  			}
>  
>  			if (odp_unlikely(pkts_ok != pkts))
> diff --git a/platform/linux-generic/odp_packet_io.c b/platform/linux-generic/odp_packet_io.c
> index 5ae24b9..6e7db41 100644
> --- a/platform/linux-generic/odp_packet_io.c
> +++ b/platform/linux-generic/odp_packet_io.c
> @@ -564,6 +564,9 @@ int pktout_enqueue(queue_entry_t *qentry, odp_buffer_hdr_t *buf_hdr)
>  	int nbr;
>  
>  	nbr = odp_pktio_send(qentry->s.pktout, &pkt, len);
> +	if (odp_unlikely(nbr == 0))
> +		odp_packet_free(pkt);
> +

Why do this here rather than letting the caller of enqueue decide how
to deal with the failure?

>  	return (nbr == len ? 0 : -1);
>  }
>  
> @@ -583,7 +586,12 @@ int pktout_enq_multi(queue_entry_t *qentry, odp_buffer_hdr_t *buf_hdr[],
>  	for (i = 0; i < num; ++i)
>  		pkt_tbl[i] = _odp_packet_from_buffer(buf_hdr[i]->handle.handle);
>  
> -	nbr = odp_pktio_send(qentry->s.pktout, pkt_tbl, num);
> +	i = nbr = odp_pktio_send(qentry->s.pktout, pkt_tbl, num);
> +	if (odp_unlikely(i < num))
> +		do
> +			odp_packet_free(pkt_tbl[i]);
> +		while (++i < num);
> +

Same comment as above.

>  	return nbr;
>  }
>  
> diff --git a/platform/linux-generic/odp_packet_socket.c b/platform/linux-generic/odp_packet_socket.c
> index 9272146..309980d 100644
> --- a/platform/linux-generic/odp_packet_socket.c
> +++ b/platform/linux-generic/odp_packet_socket.c
> @@ -301,9 +301,6 @@ int send_pkt_sock_basic(pkt_sock_t *const pkt_sock,
>  	}			/* end while */
>  	nb_tx = i;
>  
> -	for (i = 0; i < len; i++)
> -		odp_packet_free(pkt_table[i]);
> -

Now packets that were actually sent are being leaked.

>  	return nb_tx;
>  }
>  
> @@ -408,9 +405,6 @@ int send_pkt_sock_mmsg(pkt_sock_t *const pkt_sock,
>  		flags = 0;	/* blocking for next rounds */
>  	}
>  
> -	for (i = 0; i < len; i++)
> -		odp_packet_free(pkt_table[i]);
> -

And here.

>  	return len;
>  }
>  
> diff --git a/test/performance/odp_l2fwd.c b/test/performance/odp_l2fwd.c
> index 5d4b833..8c64a9c 100644
> --- a/test/performance/odp_l2fwd.c
> +++ b/test/performance/odp_l2fwd.c
> @@ -224,8 +224,15 @@ static void *pktio_ifburst_thread(void *arg)
>  
>  		/* Drop packets with errors */
>  		pkts_ok = drop_err_pkts(pkt_tbl, pkts);
> -		if (pkts_ok > 0)
> -			odp_pktio_send(pktio_dst, pkt_tbl, pkts_ok);
> +		if (pkts_ok > 0) {
> +			int sent = odp_pktio_send(pktio_dst, pkt_tbl, pkts_ok);
> +			if (odp_unlikely(sent < pkts_ok)) {
> +				stats->drops += pkts_ok - sent;
> +				do
> +					odp_packet_free(pkt_tbl[sent]);
> +				while (++sent < pkts_ok);
> +			}
> +		}
>  
>  		if (odp_unlikely(pkts_ok != pkts))
>  			stats->drops += pkts - pkts_ok;
> -- 
> 1.9.1
>
Zoltan Kiss June 3, 2015, 5:56 p.m. UTC | #2
On 03/06/15 18:00, Stuart Haslam wrote:
> On Wed, Jun 03, 2015 at 05:44:19PM +0100, Zoltan Kiss wrote:
>> And change the behaviour in linux-generic implementation, where it's releasing
>> it during the call.
>>
>> Signed-off-by: Zoltan Kiss <zoltan.kiss@linaro.org>
>> ---
>>   example/packet/odp_pktio.c                 |  9 ++++++++-
>>   platform/linux-generic/odp_packet_io.c     | 10 +++++++++-
>>   platform/linux-generic/odp_packet_socket.c |  6 ------
>>   test/performance/odp_l2fwd.c               | 11 +++++++++--
>>   4 files changed, 26 insertions(+), 10 deletions(-)
>>
>> diff --git a/example/packet/odp_pktio.c b/example/packet/odp_pktio.c
>> index f08d9f4..4242caa 100644
>> --- a/example/packet/odp_pktio.c
>> +++ b/example/packet/odp_pktio.c
>> @@ -282,9 +282,16 @@ static void *pktio_ifburst_thread(void *arg)
>>   			/* Drop packets with errors */
>>   			pkts_ok = drop_err_pkts(pkt_tbl, pkts);
>>   			if (pkts_ok > 0) {
>> +				int sent;
>>   				/* Swap Eth MACs and IP-addrs */
>>   				swap_pkt_addrs(pkt_tbl, pkts_ok);
>> -				odp_pktio_send(pktio, pkt_tbl, pkts_ok);
>> +				sent = odp_pktio_send(pktio, pkt_tbl, pkts_ok);
>> +				if (odp_unlikely(sent < pkts_ok)) {
>> +					err_cnt += pkts_ok - sent;
>> +					do
>> +						odp_packet_free(pkt_tbl[sent++]);
>> +					while (++sent < pkts_ok);
>
> You're incrementing sent twice.

Indeed, I'll fix that.

>
>> +				}
>>   			}
>>
>>   			if (odp_unlikely(pkts_ok != pkts))
>> diff --git a/platform/linux-generic/odp_packet_io.c b/platform/linux-generic/odp_packet_io.c
>> index 5ae24b9..6e7db41 100644
>> --- a/platform/linux-generic/odp_packet_io.c
>> +++ b/platform/linux-generic/odp_packet_io.c
>> @@ -564,6 +564,9 @@ int pktout_enqueue(queue_entry_t *qentry, odp_buffer_hdr_t *buf_hdr)
>>   	int nbr;
>>
>>   	nbr = odp_pktio_send(qentry->s.pktout, &pkt, len);
>> +	if (odp_unlikely(nbr == 0))
>> +		odp_packet_free(pkt);
>> +
>
> Why do this here rather than letting the caller of enqueue decide how
> to deal with the failure?

This is bringing up an another issue: we should define the same 
behaviour for odp_queue_enq() and odp_queue_enq_multi(), shouldn't we?

>
>>   	return (nbr == len ? 0 : -1);
>>   }
>>
>> @@ -583,7 +586,12 @@ int pktout_enq_multi(queue_entry_t *qentry, odp_buffer_hdr_t *buf_hdr[],
>>   	for (i = 0; i < num; ++i)
>>   		pkt_tbl[i] = _odp_packet_from_buffer(buf_hdr[i]->handle.handle);
>>
>> -	nbr = odp_pktio_send(qentry->s.pktout, pkt_tbl, num);
>> +	i = nbr = odp_pktio_send(qentry->s.pktout, pkt_tbl, num);
>> +	if (odp_unlikely(i < num))
>> +		do
>> +			odp_packet_free(pkt_tbl[i]);
>> +		while (++i < num);
>> +
>
> Same comment as above.
>
>>   	return nbr;
>>   }
>>
>> diff --git a/platform/linux-generic/odp_packet_socket.c b/platform/linux-generic/odp_packet_socket.c
>> index 9272146..309980d 100644
>> --- a/platform/linux-generic/odp_packet_socket.c
>> +++ b/platform/linux-generic/odp_packet_socket.c
>> @@ -301,9 +301,6 @@ int send_pkt_sock_basic(pkt_sock_t *const pkt_sock,
>>   	}			/* end while */
>>   	nb_tx = i;
>>
>> -	for (i = 0; i < len; i++)
>> -		odp_packet_free(pkt_table[i]);
>> -
>
> Now packets that were actually sent are being leaked.
I'll fix these too

>
>>   	return nb_tx;
>>   }
>>
>> @@ -408,9 +405,6 @@ int send_pkt_sock_mmsg(pkt_sock_t *const pkt_sock,
>>   		flags = 0;	/* blocking for next rounds */
>>   	}
>>
>> -	for (i = 0; i < len; i++)
>> -		odp_packet_free(pkt_table[i]);
>> -
>
> And here.
>
>>   	return len;
>>   }
>>
>> diff --git a/test/performance/odp_l2fwd.c b/test/performance/odp_l2fwd.c
>> index 5d4b833..8c64a9c 100644
>> --- a/test/performance/odp_l2fwd.c
>> +++ b/test/performance/odp_l2fwd.c
>> @@ -224,8 +224,15 @@ static void *pktio_ifburst_thread(void *arg)
>>
>>   		/* Drop packets with errors */
>>   		pkts_ok = drop_err_pkts(pkt_tbl, pkts);
>> -		if (pkts_ok > 0)
>> -			odp_pktio_send(pktio_dst, pkt_tbl, pkts_ok);
>> +		if (pkts_ok > 0) {
>> +			int sent = odp_pktio_send(pktio_dst, pkt_tbl, pkts_ok);
>> +			if (odp_unlikely(sent < pkts_ok)) {
>> +				stats->drops += pkts_ok - sent;
>> +				do
>> +					odp_packet_free(pkt_tbl[sent]);
>> +				while (++sent < pkts_ok);
>> +			}
>> +		}
>>
>>   		if (odp_unlikely(pkts_ok != pkts))
>>   			stats->drops += pkts - pkts_ok;
>> --
>> 1.9.1
>>
>
Zoltan Kiss June 3, 2015, 6:42 p.m. UTC | #3
On that note it turned out this will be a lot bigger than I thought ...
And I already have two questions:

- what should we do if an assert over odp_queue_enq fails? E.g. 
"CU_ASSERT(odp_queue_enq(queue, ev) == 0)" Should we free the events, or 
just leave it?
- there are numerous places where you don't know what kind of event you 
are actually queue, so should we make an odp_event_free() call?, e.g.
odp_schedule_release_atomic



On 03/06/15 18:56, Zoltan Kiss wrote:
>
> This is bringing up an another issue: we should define the same
> behaviour for odp_queue_enq() and odp_queue_enq_multi(), shouldn't we?
Ola Liljedahl June 3, 2015, 7:37 p.m. UTC | #4
On 3 June 2015 at 20:42, Zoltan Kiss <zoltan.kiss@linaro.org> wrote:

> On that note it turned out this will be a lot bigger than I thought ...
> And I already have two questions:
>
> - what should we do if an assert over odp_queue_enq fails? E.g.
> "CU_ASSERT(odp_queue_enq(queue, ev) == 0)" Should we free the events, or
> just leave it?
>
If we want the validation program to terminate cleanly, I assume the caller
has to free any buffers that weren't enqueued or they will be leaked and
then things will go bad when the program is terminating.


> - there are numerous places where you don't know what kind of event you
> are actually queue, so should we make an odp_event_free() call?, e.g.
>
I have also encountered this situation. I even started to write a post to
the list about it. But in the end I hacked around it, probably by switching
on the event type and calling different free functions. Having an
odp_event_free() would be simpler and clearer so I second your suggestion
here.

-- Ola


> odp_schedule_release_atomic
>
>
>
> On 03/06/15 18:56, Zoltan Kiss wrote:
>
>>
>> This is bringing up an another issue: we should define the same
>> behaviour for odp_queue_enq() and odp_queue_enq_multi(), shouldn't we?
>>
> _______________________________________________
> lng-odp mailing list
> lng-odp@lists.linaro.org
> https://lists.linaro.org/mailman/listinfo/lng-odp
>
Stuart Haslam June 4, 2015, 9:18 a.m. UTC | #5
On Wed, Jun 03, 2015 at 06:56:00PM +0100, Zoltan Kiss wrote:
> 
> 
> On 03/06/15 18:00, Stuart Haslam wrote:
> >On Wed, Jun 03, 2015 at 05:44:19PM +0100, Zoltan Kiss wrote:
> >>And change the behaviour in linux-generic implementation, where it's releasing
> >>it during the call.
> >>
> >>Signed-off-by: Zoltan Kiss <zoltan.kiss@linaro.org>
> >>---
> >>  example/packet/odp_pktio.c                 |  9 ++++++++-
> >>  platform/linux-generic/odp_packet_io.c     | 10 +++++++++-
> >>  platform/linux-generic/odp_packet_socket.c |  6 ------
> >>  test/performance/odp_l2fwd.c               | 11 +++++++++--
> >>  4 files changed, 26 insertions(+), 10 deletions(-)
> >>
> >>diff --git a/example/packet/odp_pktio.c b/example/packet/odp_pktio.c
> >>index f08d9f4..4242caa 100644
> >>--- a/example/packet/odp_pktio.c
> >>+++ b/example/packet/odp_pktio.c
> >>@@ -282,9 +282,16 @@ static void *pktio_ifburst_thread(void *arg)
> >>  			/* Drop packets with errors */
> >>  			pkts_ok = drop_err_pkts(pkt_tbl, pkts);
> >>  			if (pkts_ok > 0) {
> >>+				int sent;
> >>  				/* Swap Eth MACs and IP-addrs */
> >>  				swap_pkt_addrs(pkt_tbl, pkts_ok);
> >>-				odp_pktio_send(pktio, pkt_tbl, pkts_ok);
> >>+				sent = odp_pktio_send(pktio, pkt_tbl, pkts_ok);
> >>+				if (odp_unlikely(sent < pkts_ok)) {
> >>+					err_cnt += pkts_ok - sent;
> >>+					do
> >>+						odp_packet_free(pkt_tbl[sent++]);
> >>+					while (++sent < pkts_ok);
> >
> >You're incrementing sent twice.
> 
> Indeed, I'll fix that.
> 
> >
> >>+				}
> >>  			}
> >>
> >>  			if (odp_unlikely(pkts_ok != pkts))
> >>diff --git a/platform/linux-generic/odp_packet_io.c b/platform/linux-generic/odp_packet_io.c
> >>index 5ae24b9..6e7db41 100644
> >>--- a/platform/linux-generic/odp_packet_io.c
> >>+++ b/platform/linux-generic/odp_packet_io.c
> >>@@ -564,6 +564,9 @@ int pktout_enqueue(queue_entry_t *qentry, odp_buffer_hdr_t *buf_hdr)
> >>  	int nbr;
> >>
> >>  	nbr = odp_pktio_send(qentry->s.pktout, &pkt, len);
> >>+	if (odp_unlikely(nbr == 0))
> >>+		odp_packet_free(pkt);
> >>+
> >
> >Why do this here rather than letting the caller of enqueue decide how
> >to deal with the failure?
> 
> This is bringing up an another issue: we should define the same
> behaviour for odp_queue_enq() and odp_queue_enq_multi(), shouldn't
> we?
> 

Yes, Leo mentioned that in the egress API document.

> >
> >>  	return (nbr == len ? 0 : -1);
> >>  }
> >>
> >>@@ -583,7 +586,12 @@ int pktout_enq_multi(queue_entry_t *qentry, odp_buffer_hdr_t *buf_hdr[],
> >>  	for (i = 0; i < num; ++i)
> >>  		pkt_tbl[i] = _odp_packet_from_buffer(buf_hdr[i]->handle.handle);
> >>
> >>-	nbr = odp_pktio_send(qentry->s.pktout, pkt_tbl, num);
> >>+	i = nbr = odp_pktio_send(qentry->s.pktout, pkt_tbl, num);
> >>+	if (odp_unlikely(i < num))
> >>+		do
> >>+			odp_packet_free(pkt_tbl[i]);
> >>+		while (++i < num);
> >>+
> >
> >Same comment as above.
> >
> >>  	return nbr;
> >>  }
> >>
> >>diff --git a/platform/linux-generic/odp_packet_socket.c b/platform/linux-generic/odp_packet_socket.c
> >>index 9272146..309980d 100644
> >>--- a/platform/linux-generic/odp_packet_socket.c
> >>+++ b/platform/linux-generic/odp_packet_socket.c
> >>@@ -301,9 +301,6 @@ int send_pkt_sock_basic(pkt_sock_t *const pkt_sock,
> >>  	}			/* end while */
> >>  	nb_tx = i;
> >>
> >>-	for (i = 0; i < len; i++)
> >>-		odp_packet_free(pkt_table[i]);
> >>-
> >
> >Now packets that were actually sent are being leaked.
> I'll fix these too
> 

By the way I submitted a patch (https://patches.linaro.org/46723/) a
while back to tidy up the handling of transmit errors in odp_packet_socket.c.
There's an outstanding comment I need to address, I'll send an updated
version later today or tomorrow.
Stuart Haslam June 4, 2015, 9:22 a.m. UTC | #6
On Wed, Jun 03, 2015 at 09:37:54PM +0200, Ola Liljedahl wrote:
> On 3 June 2015 at 20:42, Zoltan Kiss <zoltan.kiss@linaro.org> wrote:
> 
> > On that note it turned out this will be a lot bigger than I thought ...
> > And I already have two questions:
> >
> > - what should we do if an assert over odp_queue_enq fails? E.g.
> > "CU_ASSERT(odp_queue_enq(queue, ev) == 0)" Should we free the events, or
> > just leave it?
> >
> If we want the validation program to terminate cleanly, I assume the caller
> has to free any buffers that weren't enqueued or they will be leaked and
> then things will go bad when the program is terminating.
> 
> 
> > - there are numerous places where you don't know what kind of event you
> > are actually queue, so should we make an odp_event_free() call?, e.g.
> >
> I have also encountered this situation. I even started to write a post to
> the list about it. But in the end I hacked around it, probably by switching
> on the event type and calling different free functions. Having an
> odp_event_free() would be simpler and clearer so I second your suggestion
> here.
> 
> -- Ola

Me too. Actually it looks like I did;

odp_buffer_free(odp_buffer_from_event(ev));

Is this not sufficient?.. anyway I agree odp_event_free() would be
clearer.
Zoltan Kiss June 4, 2015, 2:15 p.m. UTC | #7
On 04/06/15 10:22, Stuart Haslam wrote:
> On Wed, Jun 03, 2015 at 09:37:54PM +0200, Ola Liljedahl wrote:
>> On 3 June 2015 at 20:42, Zoltan Kiss <zoltan.kiss@linaro.org> wrote:
>>
>>> On that note it turned out this will be a lot bigger than I thought ...
>>> And I already have two questions:
>>>
>>> - what should we do if an assert over odp_queue_enq fails? E.g.
>>> "CU_ASSERT(odp_queue_enq(queue, ev) == 0)" Should we free the events, or
>>> just leave it?
>>>
>> If we want the validation program to terminate cleanly, I assume the caller
>> has to free any buffers that weren't enqueued or they will be leaked and
>> then things will go bad when the program is terminating.
>>
>>
>>> - there are numerous places where you don't know what kind of event you
>>> are actually queue, so should we make an odp_event_free() call?, e.g.
>>>
>> I have also encountered this situation. I even started to write a post to
>> the list about it. But in the end I hacked around it, probably by switching
>> on the event type and calling different free functions. Having an
>> odp_event_free() would be simpler and clearer so I second your suggestion
>> here.
>>
>> -- Ola
>
> Me too. Actually it looks like I did;
>
> odp_buffer_free(odp_buffer_from_event(ev));
>
> Is this not sufficient?..
No, it releases the buffer as it would be a raw buffer. A related issue 
is that odp_buffer_* functions seem to handle the input as a raw buffer, 
while an unsuspecting developer might think they are generic buffer 
functions which can handle any kind of buffers.

  anyway I agree odp_event_free() would be
> clearer.

I'll send in a patch for that too
Ola Liljedahl June 8, 2015, 2:15 p.m. UTC | #8
On 4 June 2015 at 11:22, Stuart Haslam <stuart.haslam@linaro.org> wrote:

> On Wed, Jun 03, 2015 at 09:37:54PM +0200, Ola Liljedahl wrote:
> > On 3 June 2015 at 20:42, Zoltan Kiss <zoltan.kiss@linaro.org> wrote:
> >
> > > On that note it turned out this will be a lot bigger than I thought ...
> > > And I already have two questions:
> > >
> > > - what should we do if an assert over odp_queue_enq fails? E.g.
> > > "CU_ASSERT(odp_queue_enq(queue, ev) == 0)" Should we free the events,
> or
> > > just leave it?
> > >
> > If we want the validation program to terminate cleanly, I assume the
> caller
> > has to free any buffers that weren't enqueued or they will be leaked and
> > then things will go bad when the program is terminating.
> >
> >
> > > - there are numerous places where you don't know what kind of event you
> > > are actually queue, so should we make an odp_event_free() call?, e.g.
> > >
> > I have also encountered this situation. I even started to write a post to
> > the list about it. But in the end I hacked around it, probably by
> switching
> > on the event type and calling different free functions. Having an
> > odp_event_free() would be simpler and clearer so I second your suggestion
> > here.
> >
> > -- Ola
>
> Me too. Actually it looks like I did;
>
> odp_buffer_free(odp_buffer_from_event(ev));
>
But this require the code snippet here to be aware of *all* different
(current and *future*) event types.

-- Ola


> Is this not sufficient?.. anyway I agree odp_event_free() would be
> clearer.
>
> --
> Stuart.
>
diff mbox

Patch

diff --git a/example/packet/odp_pktio.c b/example/packet/odp_pktio.c
index f08d9f4..4242caa 100644
--- a/example/packet/odp_pktio.c
+++ b/example/packet/odp_pktio.c
@@ -282,9 +282,16 @@  static void *pktio_ifburst_thread(void *arg)
 			/* Drop packets with errors */
 			pkts_ok = drop_err_pkts(pkt_tbl, pkts);
 			if (pkts_ok > 0) {
+				int sent;
 				/* Swap Eth MACs and IP-addrs */
 				swap_pkt_addrs(pkt_tbl, pkts_ok);
-				odp_pktio_send(pktio, pkt_tbl, pkts_ok);
+				sent = odp_pktio_send(pktio, pkt_tbl, pkts_ok);
+				if (odp_unlikely(sent < pkts_ok)) {
+					err_cnt += pkts_ok - sent;
+					do
+						odp_packet_free(pkt_tbl[sent++]);
+					while (++sent < pkts_ok);
+				}
 			}
 
 			if (odp_unlikely(pkts_ok != pkts))
diff --git a/platform/linux-generic/odp_packet_io.c b/platform/linux-generic/odp_packet_io.c
index 5ae24b9..6e7db41 100644
--- a/platform/linux-generic/odp_packet_io.c
+++ b/platform/linux-generic/odp_packet_io.c
@@ -564,6 +564,9 @@  int pktout_enqueue(queue_entry_t *qentry, odp_buffer_hdr_t *buf_hdr)
 	int nbr;
 
 	nbr = odp_pktio_send(qentry->s.pktout, &pkt, len);
+	if (odp_unlikely(nbr == 0))
+		odp_packet_free(pkt);
+
 	return (nbr == len ? 0 : -1);
 }
 
@@ -583,7 +586,12 @@  int pktout_enq_multi(queue_entry_t *qentry, odp_buffer_hdr_t *buf_hdr[],
 	for (i = 0; i < num; ++i)
 		pkt_tbl[i] = _odp_packet_from_buffer(buf_hdr[i]->handle.handle);
 
-	nbr = odp_pktio_send(qentry->s.pktout, pkt_tbl, num);
+	i = nbr = odp_pktio_send(qentry->s.pktout, pkt_tbl, num);
+	if (odp_unlikely(i < num))
+		do
+			odp_packet_free(pkt_tbl[i]);
+		while (++i < num);
+
 	return nbr;
 }
 
diff --git a/platform/linux-generic/odp_packet_socket.c b/platform/linux-generic/odp_packet_socket.c
index 9272146..309980d 100644
--- a/platform/linux-generic/odp_packet_socket.c
+++ b/platform/linux-generic/odp_packet_socket.c
@@ -301,9 +301,6 @@  int send_pkt_sock_basic(pkt_sock_t *const pkt_sock,
 	}			/* end while */
 	nb_tx = i;
 
-	for (i = 0; i < len; i++)
-		odp_packet_free(pkt_table[i]);
-
 	return nb_tx;
 }
 
@@ -408,9 +405,6 @@  int send_pkt_sock_mmsg(pkt_sock_t *const pkt_sock,
 		flags = 0;	/* blocking for next rounds */
 	}
 
-	for (i = 0; i < len; i++)
-		odp_packet_free(pkt_table[i]);
-
 	return len;
 }
 
diff --git a/test/performance/odp_l2fwd.c b/test/performance/odp_l2fwd.c
index 5d4b833..8c64a9c 100644
--- a/test/performance/odp_l2fwd.c
+++ b/test/performance/odp_l2fwd.c
@@ -224,8 +224,15 @@  static void *pktio_ifburst_thread(void *arg)
 
 		/* Drop packets with errors */
 		pkts_ok = drop_err_pkts(pkt_tbl, pkts);
-		if (pkts_ok > 0)
-			odp_pktio_send(pktio_dst, pkt_tbl, pkts_ok);
+		if (pkts_ok > 0) {
+			int sent = odp_pktio_send(pktio_dst, pkt_tbl, pkts_ok);
+			if (odp_unlikely(sent < pkts_ok)) {
+				stats->drops += pkts_ok - sent;
+				do
+					odp_packet_free(pkt_tbl[sent]);
+				while (++sent < pkts_ok);
+			}
+		}
 
 		if (odp_unlikely(pkts_ok != pkts))
 			stats->drops += pkts - pkts_ok;