01-25-2012 11:00 AM
I am observing what looks like a regression in NDK 2 Beta 3, relative to prior releases including Beta 2 and NDK 1. What is happening is that custom events created and pushed (bps_event_create and bps_push_event) from any thread other than the one running the event loop, are never delivered to the event loop.
I first noticed this in an app previously working fine under Beta 2, and deployed to a real device running and OS 2 build. When the app was rebuilt under Beta 3, custom events started to disappear.
I boiled the code down to a bare minimum with a bps event loop, and a separate heartbeat thread. The event loop times out every 5 seconds, and calls a function to post a new custom event, which is subsequently received by the event loop itself. The heartbeat thread calls the same posting function every 7 seconds, obviously from a separate thread which is just a delay timer.
The identical test code has been compiled on NDK 1, NDK 2 Beta 2, and NDK 2 Beta 3. The NDK 1 version was deployed to a device running 220.127.116.1185. Both Beta 2 and Beta 3 versions were deployed to a device running 18.104.22.16811.
In all cases except one, all of the posted events were received and processed by the bps event loop. However the version compiled under Beta 3 receives only the events posted to the queue by the natural timeout in the event loop; the events posted by the heartbeat thread were never received. I did verify that BPS_SUCCESS was returned from the event create and push calls, in all cases.
On the Beta 3 version, I also tried adding a bps_initialize to the heartbeat thread, which also returned BPS_SUCCESS, but made no difference to the missing events.
The bad behavior does not seem to depend on the build deployed in the device. Both devices show the correct behavior, regardless of the OS installed, as long as the app was not compiled with Beta 3. But when compiled with Beta 3, the app messages from outside the event loop thread were accepted, consumed, but never delivered.
Has this been observed anywhere else? I looked in the issue tracker and found nothing similar. I can provide a zip file with the bare bones code to demonstrate.
(BTW I have also played with the new channel event stuff in Beta 3, with the same missing event results, but that is not part of the demo code...the demo is just pure default channel handling).
01-25-2012 11:31 AM
With 2.0 Beta 3, a new version of bps was released (libbps.so.3) which has changed how events are posted and delivered when using multiple threads. The old mechanics are still available in libbps.so.2 (as you mentioned).
For your sample code to work, you need to push events using bps_channel_push_event() and provide the channel ID that is being used by the thread that is calling bps_get_event(). You can get the channel ID by calling bps_channel_get_active().
Here's a bit more background information:
A BPS channel is an event queue (and some other stuff) that is owned by the thread it is created on. A channel cannot be shared between threads. Each thread that uses BPS will automatically get a channel created for it, and that default channel will be set as the active one. When calling bps_get_event(), a thread is trying to pop an event off of the currently active channel for its thread. When calling bps_push_event(), a thread is posting an event to its currently active channel for the thread.
With all that in place, we needed to provide a means for developers to post events across threads. Which is why bps_channel_push_event() was introduced.
So for your particular case. One thread is pushing events onto its active channel, and a different thread is getting events from its active channel. These 2 channels will be different because they are different threads. So you have to change the bps_push_event() call to a bps_channel_push_event() call.
01-25-2012 11:52 AM
We seem to be stumbling around here because the Help system with Beta 3, when referring to BPS channels, uses phrases like " Some applications may wish to create additional channels and associate different services to different channels. " But it seems like it is now mandatory, not "may".
I don't have a problem adapting to a new way of doing things, but there are very few clues in the release notes, or the Help system, as to what to do. I did do a separate implementation in my app (not the demo code I mentioned earlier) that does create a separate thread for an app event loop, creates a channel, and posts to it with reference to the channel id, but I still observed missing events. I will go back and double-check that later today.
But meanwhile, in the docs...
The flags parameter to bps_channel_create() is described as "The flags parameter passed to ChannelCreate()". Since that's a microkernel call not part of our IDE environment, I googled that function and the results showed a bunch of flag symbols that don't relate back to symbols in the Beta 3 environment, and leave one puzzled as to what (if any) flags should actually be used.
The function bps_channel_get_event() is in the Help system, but does not exist in the bps.h header along with the other channel functions. One is tempted to use this function, but a compile error results because the prototype is not defined anywhere. The conclusion from this is that, maybe, the bps_channel_set_active() call associates the channel id with the thread, and therefore the event loop, so that bps_get_event() knows what channel to use...but this is deduced, not intuitive. Maybe I should really be using bps_channel_get_event(), which seems appropriate, but isn't defined. Using the other function may or may not account for missing messages never received in the event loop.
So yeah we can do this, but it seems there are some important things missing...
01-25-2012 01:34 PM
I understand the confusion with the docs, they have undergone some changes that should address the ambiguity surrounding channels and their use (once the updates make it to the site). With regards to your comments:
You shouldn't have to explicitly create a channel on the thread. Calling bps_initialize() at thread startup will create a channel for you, and set it as the active channel (just make sure to call bps_shutdown() on thread termination to keep the correct ref count). After calling bps_initialize(), call bps_channel_get_active() to get the channel ID that was created for you.
Thread ownership for a channel cannot be changed. The reason we have bps_channel_set_active() is to support threads that create multiple channels. A thread can own many channels, so when a call comes in for bps_get_event(), BPS needs to figure out which of the channel event queues it should fetch from, so BPS will fetch events from the active channel.
The rationale around having multiple channels for a single thread is to support libraries that want to make use of BPS. A library would be able to create its own channel that the rest of the app wouldn't know about. When a library function is called, the created channel would be made active for the duration of the call (keeping events isolated). There are other potential uses here, but that was the main reason to have it.
01-25-2012 03:14 PM
Thanks for your clarifications, that has been a big help. I now have a functioning thread that is happily receiving custom app events posted from other threads in the app, without any disappearing, and using the channel id.
The docs do need some work with the new channel APIs. For example, the description of bps_initialize() says
Thebps_initialize()function initializes the BPS library. This function should be the first BPS library function that you call in your application... An application may callbps_initialize()more than once..."
The emphasis on initializing the library, and making it the first BPS call in the app, seemingly conflicts with the later statement about calling it more than once. Going back to NDK 1.0 when I started working with this, there didn't seem to be any reason to call bps_initialize() multiple times. Now I understand that if you want to have separate threads handling different events, via the new channel API, it makes sense...but the docs don't hint at the concept of why and how to go about using BPS in multiple threads, and what are the implications. Now, for anything other than the simple case of posting a new event during the processing of another event, on the same thread and event loop, bps_push_event() that we have always used is no longer valid. But it will return successfully, posting to the wrong queue, with the end result being events that just disappear. This is a critical point for apps that use custom events.