Introduce optional javascript semantics for event handling

- new CONFIG option now allows the user to choose between ordered, serial
    processing of event handlers and parallel execution of events (JS
semantics) OR parallel processing of event handlers and serial execution of
events (legacy mciro:bit semantics)

  - Introduciton of a MicroBitLock primitive for mutual exclusion
This commit is contained in:
Joe Finney 2018-09-27 17:51:17 +01:00
parent 3898c0ddc7
commit 9dedea4df2
7 changed files with 197 additions and 6 deletions

View File

@ -171,6 +171,21 @@ extern uint32_t __etext;
#define MESSAGE_BUS_LISTENER_MAX_QUEUE_DEPTH 10
#endif
//
// Define MESSAGE_BUS concurrency behaviour.
// Set to MESSAGE_BUS_CONCURRENT_LISTENERS to fire event handler
// concurrently when a given event is raised, and process events sequentially as they arrive (default micro:bit semantics).
// Set to MESSAGE_BUS_CONCURRENT_EVENTS to to fire event handlers sequentially for any given event, while still allowing
// concurrent processing of events.
//
//
// Permissable values are:
// 0: MESSAGE_BUS_CONCURRENT_LISTENERS
// 1: MESSAGE_BUS_CONCURRENT_EVENTS
//
#ifndef MESSAGE_BUS_CONCURRENCY_MODE
#define MESSAGE_BUS_CONCURRENCY_MODE MESSAGE_BUS_CONCURRENT_LISTENERS
#endif
//
// Core micro:bit services
//

View File

@ -28,9 +28,9 @@ DEALINGS IN THE SOFTWARE.
#include "mbed.h"
#include "MicroBitConfig.h"
#include "MicroBitLock.h"
#include "MicroBitEvent.h"
#include "MemberFunctionCallback.h"
#include "MicroBitConfig.h"
// MicroBitListener flags...
#define MESSAGE_BUS_LISTENER_PARAMETERISED 0x0001
@ -67,7 +67,7 @@ struct MicroBitListener
MicroBitEvent evt;
MicroBitEventQueueItem *evt_queue;
MicroBitLock lock;
MicroBitListener *next;
/**

66
inc/core/MicroBitLock.h Normal file
View File

@ -0,0 +1,66 @@
/*
The MIT License (MIT)
Copyright (c) 2016 British Broadcasting Corporation.
This software is provided by Lancaster University by arrangement with the BBC.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
/**
* A simple lock, mostly used for mutual exclusion.
*/
#ifndef MICROBIT_LOCK_H
#define MICROBIT_LOCK_H
#include "MicroBitConfig.h"
class Fiber;
class MicroBitLock
{
private:
bool locked;
Fiber *queue;
public:
/**
* Create a new lock that can be used for mutual exclusion and condition synchronisation.
*/
MicroBitLock();
/**
* Block the calling fiber until the lock is available
**/
void wait();
/**
* Release the lock, and signal to one waiting fiber to continue
*/
void notify();
/**
* Release the lock, and signal to all waiting fibers to continue
*/
void notifyAll();
};
#endif

View File

@ -33,6 +33,9 @@ DEALINGS IN THE SOFTWARE.
#include "MicroBitListener.h"
#include "EventModel.h"
#define MESSAGE_BUS_CONCURRENT_LISTENERS 0
#define MESSAGE_BUS_CONCURRENT_EVENTS 1
/**
* Class definition for the MicroBitMessageBus.
*
@ -144,7 +147,6 @@ class MicroBitMessageBus : public EventModel, public MicroBitComponent
MicroBitListener *listeners; // Chain of active listeners.
MicroBitEventQueueItem *evt_queue_head; // Head of queued events to be processed.
MicroBitEventQueueItem *evt_queue_tail; // Tail of queued events to be processed.
uint16_t nonce_val; // The last nonce issued.
uint16_t queueLength; // The number of events currently waiting to be processed.
/**

View File

@ -171,4 +171,8 @@
#define MICROBIT_BLE_DEVICE_INFORMATION_SERVICE YOTTA_CFG_MICROBIT_DAL_BLUETOOTH_DEVICE_INFO_SERVICE
#endif
#ifdef YOTTA_CFG_MICROBIT_DAL_MESSAGE_BUS_CONCURRENCY_MODE
#define MESSAGE_BUS_CONCURRENCY_MODE YOTTA_CFG_MICROBIT_DAL_MESSAGE_BUS_CONCURRENCY_MODE
#endif
#endif

View File

@ -942,3 +942,84 @@ void idle_task()
schedule();
}
}
/**
* Create a new lock that can be used for mutual exclusion and condition synchronisation.
*/
MicroBitLock::MicroBitLock()
{
queue = NULL;
locked = false;
}
/**
* Block the calling fiber until the lock is available
**/
void MicroBitLock::wait()
{
Fiber *f = currentFiber;
// If the scheduler is not running, then simply exit, as we're running monothreaded.
if (!fiber_scheduler_running())
return;
if (locked)
{
// wait() is a blocking call, so if we're in a fork on block context,
// it's time to spawn a new fiber...
if (currentFiber->flags & MICROBIT_FIBER_FLAG_FOB)
{
// Allocate a new fiber. This will come from the fiber pool if availiable,
// else a new one will be allocated on the heap.
forkedFiber = getFiberContext();
// If we're out of memory, there's nothing we can do.
// keep running in the context of the current thread as a best effort.
if (forkedFiber != NULL)
f = forkedFiber;
}
// Remove fiber from the run queue
dequeue_fiber(f);
// Add fiber to the sleep queue. We maintain strict ordering here to reduce lookup times.
queue_fiber(f, &queue);
// Finally, enter the scheduler.
schedule();
}
locked = true;
}
/**
* Release the lock, and signal to one waiting fiber to continue
*/
void MicroBitLock::notify()
{
Fiber *f = queue;
if (f)
{
dequeue_fiber(f);
queue_fiber(f, &runQueue);
}
locked = false;
}
/**
* Release the lock, and signal to all waiting fibers to continue
*/
void MicroBitLock::notifyAll()
{
Fiber *f = queue;
while (f)
{
dequeue_fiber(f);
queue_fiber(f, &runQueue);
f = queue;
}
locked = false;
}

View File

@ -95,14 +95,19 @@ void async_callback(void *param)
// Queue this event up for later, if that's how we've been configured.
if (listener->flags & MESSAGE_BUS_LISTENER_QUEUE_IF_BUSY)
{
#if (MESSAGE_BUS_CONCURRENCY_MODE == MESSAGE_BUS_CONCURRENT_LISTENERS)
listener->queue(listener->evt);
return;
#endif
}
}
// Determine the calling convention for the callback, and invoke...
// C++ is really bad at this! Especially as the ARM compiler is yet to support C++ 11 :-/
#if (MESSAGE_BUS_CONCURRENCY_MODE == MESSAGE_BUS_CONCURRENT_EVENTS)
listener->lock.wait();
#endif
// Record that we have a fiber going into this listener...
listener->flags |= MESSAGE_BUS_LISTENER_BUSY;
@ -138,6 +143,10 @@ void async_callback(void *param)
// The fiber of exiting... clear our state.
listener->flags &= ~MESSAGE_BUS_LISTENER_BUSY;
#if (MESSAGE_BUS_CONCURRENCY_MODE == MESSAGE_BUS_CONCURRENT_EVENTS)
listener->lock.notify();
#endif
}
/**
@ -261,6 +270,13 @@ int MicroBitMessageBus::deleteMarkedListeners()
return removed;
}
MicroBitEvent last_event;
void process_sequentially(void *param)
{
MicroBitMessageBus *m = (MicroBitMessageBus *)param;
m->process(last_event);
}
/**
* Periodic callback from MicroBit.
*
@ -278,7 +294,12 @@ void MicroBitMessageBus::idleTick()
while (item)
{
// send the event to all standard event listeners.
#if (MESSAGE_BUS_CONCURRENCY_MODE == MESSAGE_BUS_CONCURRENT_EVENTS)
last_event = item->evt;
invoke(process_sequentially,this);
#else
this->process(item->evt);
#endif
// Free the queue item.
delete item;
@ -365,10 +386,12 @@ int MicroBitMessageBus::process(MicroBitEvent &evt, bool urgent)
// Otherwise, we invoke it in a 'fork on block' context, that will automatically create a fiber
// should the event handler attempt a blocking operation, but doesn't have the overhead
// of creating a fiber needlessly. (cool huh?)
if (l->flags & MESSAGE_BUS_LISTENER_NONBLOCKING || !fiber_scheduler_running())
async_callback(l);
else
#if (MESSAGE_BUS_CONCURRENCY_MODE == MESSAGE_BUS_CONCURRENT_LISTENERS)
if (!(l->flags & MESSAGE_BUS_LISTENER_NONBLOCKING) && fiber_scheduler_running())
invoke(async_callback, l);
else
#endif
async_callback(l);
}
else
{