bt_gatt_write_without_response_cb / bt_gatt_notify: how to prevent blocking

Hello,

calls to the above functions block the caller if internal queues become full.  Because my application is running in a big loop, I need those functions without blocking (a specific return code would suffice).

Is there any way to prevent those functions from blocking?  Or is there another non-blocking API doing similar things?

Thanks & regards

Hardy

PS: my current workaround is a small extra thread for packet transmission which is allowed to block (extra buffer, extra stack, extra copy operation).  But this seems to be like using a sledge-hammer to crack a nut

  • I think a subtle problem is that the documentation of bt_gatt_notify (link: https://docs.zephyrproject.org/latest/connectivity/bluetooth/api/gatt.html#c.bt_gatt_notify) doesn't tell us that it is blocking if called enough times without the notify attempts resolving. This can cause unbounded delays in certain scenarios and the user has no idea that this function can be blocking.

  • personally I have dealt with the issue by using bt_gatt_notfiy_cb to have a custom function called when the notify completes. That clears a flag to let me know that particular gatt request has completed. The flag is also manually cleared if we have a disconnection event with the peer being notified. Then I basically just don't use bt_gatt_notify_cb if my flag says a gatt request hasn't been resolved - no more issue with blocking.

  • This does unfortunately not help if you want to send more than one packet a time.  I'm trying to optimize for speed, because in our application data transfer is done via bt_gatt_notify*(), so I want to transfer as much packets as possible (*) per connection event.

    (*) half true: actually I have to take current consumption into account

  • I have to answer myself... ;-)

    My current trick is to delegate the actual call to bt_gatt_notify() into the system workqueue.  And how is this done?:

    where the actual call is done:

     tx_work.buf = ptrToPacketData;
     tx_work.len = numberOfBytesToWrite;
     k_work_submit_to_queue( &k_sys_work_q, &tx_work.work);
     if ( !tx_work.result)
     {
     m_hadTxPacketFifoOverflow = true;
     }
    
    The corresponding working function does roughly
    
    static void tx_work_fn(struct k_work *item)
    /**
     * Send packet to stack.
     *
     * \return
     * \a true if packet successfully put into TX queue
     */
    {
     int err;
    
     if (m_hadTxPacketFifoOverflow)
     {
     return false;
     }
     err = bt_gatt_notify(m_conn, m_attrOfSlaveSendsChar, buf, len);
     if (err != 0)
     {
     if (err == -ENOTCONN)
     {
     LOG_WRN("WAL: bt_gatt_notify_cb() not connected -> discarding");
     return true;
     }
     else if (err == -ENOMEM)
     {
     LOG_WRN("WAL: bt_gatt_notify_cb() not blocking");
     return false;
     }
     else
     {
     // we are dead
     LOG_ERR("WAL: bt_gatt_notify_cb() failed(err %d), len:%d", err, len);
     return true;
     }
     }
     return true;

    because the main thread (at least in my case) has lower priority then the system workqueue thread the first thing is more or less a synchronous call which does not block and return true/false depending on successful packet queuing.

    Simple but awkward.

    H.

    PS: data structure

    struct s_tx_work {
     k_work work;
     uint8_t *buf;
     uint16_t len;
     bool result;
    };
    
    plus
    
     k_work_init(&tx_work.work, tx_work_fn);

    PPS: don't expect the above snippets to compile. They are just there to give you an idea.

    PPPS: e.g. parameter/result have to be assigned accordingly

    PPPPS: don't make me responsible for "formatting"

Related