mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-08-20 17:05:06 +00:00
Fixes
This commit is contained in:
parent
ba59437f44
commit
a2056e8e96
@ -71,13 +71,16 @@ void set_completion(AppInstanceId app_instance_id, AppCompletionSignal* completi
|
|||||||
// Takes a reference on app_instance_id's completion signal (see AppCompletionSignal), for the
|
// Takes a reference on app_instance_id's completion signal (see AppCompletionSignal), for the
|
||||||
// caller to wait on. @return the signal to wait on, or NULL if the instance has already fully
|
// caller to wait on. @return the signal to wait on, or NULL if the instance has already fully
|
||||||
// finished (its ledger entry - and so its reference to the signal - is already gone) and so
|
// finished (its ledger entry - and so its reference to the signal - is already gone) and so
|
||||||
// there's nothing left to wait for.
|
// there's nothing left to wait for, or if the instance is still starting up (start_internal()
|
||||||
|
// in manager.cpp inserts the ledger entry before app_scheduler_start() has gotten as far as
|
||||||
|
// set_completion() - `completion` is NULL for that whole window) and so there's nothing to
|
||||||
|
// take a reference on yet.
|
||||||
AppCompletionSignal* acquire_completion_signal(AppInstanceId app_instance_id) {
|
AppCompletionSignal* acquire_completion_signal(AppInstanceId app_instance_id) {
|
||||||
auto& ledger = app_ledger();
|
auto& ledger = app_ledger();
|
||||||
mutex_lock(&ledger.mutex);
|
mutex_lock(&ledger.mutex);
|
||||||
auto iterator = ledger.instances.find(app_instance_id);
|
auto iterator = ledger.instances.find(app_instance_id);
|
||||||
AppCompletionSignal* completion = nullptr;
|
AppCompletionSignal* completion = nullptr;
|
||||||
if (iterator != ledger.instances.end()) {
|
if (iterator != ledger.instances.end() && iterator->second.completion != nullptr) {
|
||||||
completion = iterator->second.completion;
|
completion = iterator->second.completion;
|
||||||
completion->refcount++;
|
completion->refcount++;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,5 +7,5 @@ file(GLOB_RECURSE SOURCE_FILES "source/*.c*")
|
|||||||
tactility_add_module(lvgl-window-manager-module
|
tactility_add_module(lvgl-window-manager-module
|
||||||
SRCS ${SOURCE_FILES}
|
SRCS ${SOURCE_FILES}
|
||||||
INCLUDE_DIRS include/
|
INCLUDE_DIRS include/
|
||||||
REQUIRES TactilityKernel lvgl-module
|
REQUIRES TactilityKernel lvgl-module app-module
|
||||||
)
|
)
|
||||||
|
|||||||
@ -36,6 +36,10 @@ enum WindowState {
|
|||||||
* @return the widget windows should actually be placed into - @a root_widget itself, or a
|
* @return the widget windows should actually be placed into - @a root_widget itself, or a
|
||||||
* child of it. Returning NULL falls back to @a root_widget.
|
* child of it. Returning NULL falls back to @a root_widget.
|
||||||
* @warning Called on the LVGL task with the LVGL lock already held.
|
* @warning Called on the LVGL task with the LVGL lock already held.
|
||||||
|
* @warning Also called with window-manager's internal lifecycle_mutex held (non-recursive) -
|
||||||
|
* do NOT call window_manager_start()/window_manager_stop()/window_manager_create()/
|
||||||
|
* window_manager_remove() or any other window-manager API from this callback, that would
|
||||||
|
* deadlock.
|
||||||
*/
|
*/
|
||||||
typedef lv_obj_t* (*WindowManagerScreenInitFn)(lv_obj_t* root_widget);
|
typedef lv_obj_t* (*WindowManagerScreenInitFn)(lv_obj_t* root_widget);
|
||||||
|
|
||||||
@ -76,6 +80,10 @@ error_t window_manager_stop(void);
|
|||||||
* window_manager_remove() for the window that used to be on top (e.g. a dialog's own thread as
|
* window_manager_remove() for the window that used to be on top (e.g. a dialog's own thread as
|
||||||
* it closes). Do NOT rely on thread_local state set by this window's own app thread; use
|
* it closes). Do NOT rely on thread_local state set by this window's own app thread; use
|
||||||
* @a user_data instead.
|
* @a user_data instead.
|
||||||
|
* @warning Also called with window-manager's internal lifecycle_mutex held (non-recursive) -
|
||||||
|
* do NOT call window_manager_start()/window_manager_stop()/window_manager_create()/
|
||||||
|
* window_manager_remove() or any other window-manager API from this callback, that would
|
||||||
|
* deadlock.
|
||||||
*/
|
*/
|
||||||
typedef void (*WindowCreateWidgetsFn)(lv_obj_t* root, void* user_data);
|
typedef void (*WindowCreateWidgetsFn)(lv_obj_t* root, void* user_data);
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
#include <lvgl_window_manager/window_manager.h>
|
#include <lvgl_window_manager/window_manager.h>
|
||||||
|
|
||||||
#include "../../app-module/include/app/instance.h"
|
#include <app/instance.h>
|
||||||
|
|
||||||
#include <lvgl/lvgl.h>
|
#include <lvgl/lvgl.h>
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,6 @@ list(APPEND REQUIRES_LIST
|
|||||||
lvgl-module
|
lvgl-module
|
||||||
lvgl-window-manager-module
|
lvgl-window-manager-module
|
||||||
app-module
|
app-module
|
||||||
app-esp32-module
|
|
||||||
crypt-module
|
crypt-module
|
||||||
gps-module
|
gps-module
|
||||||
gps-generic-module
|
gps-generic-module
|
||||||
@ -23,6 +22,7 @@ list(APPEND REQUIRES_LIST
|
|||||||
if (DEFINED ENV{ESP_IDF_VERSION})
|
if (DEFINED ENV{ESP_IDF_VERSION})
|
||||||
|
|
||||||
list(APPEND REQUIRES_LIST
|
list(APPEND REQUIRES_LIST
|
||||||
|
app-esp32-module
|
||||||
platform-esp32
|
platform-esp32
|
||||||
driver
|
driver
|
||||||
elf_loader
|
elf_loader
|
||||||
|
|||||||
@ -27,8 +27,8 @@ typedef struct PropertiesFile PropertiesFile;
|
|||||||
* @param[in] path absolute or relative file path (e.g. "/data/settings.properties") - the
|
* @param[in] path absolute or relative file path (e.g. "/data/settings.properties") - the
|
||||||
* parent directory must already exist
|
* parent directory must already exist
|
||||||
* @return the new instance, or NULL on allocation failure, or NULL if @a path exists but a
|
* @return the new instance, or NULL on allocation failure, or NULL if @a path exists but a
|
||||||
* genuine I/O error interrupted reading it (a missing file is not an error - the instance
|
* genuine error prevented opening or reading it, e.g. a permissions error (a missing file is
|
||||||
* starts out empty in that case)
|
* not an error - the instance starts out empty in that case)
|
||||||
*/
|
*/
|
||||||
PropertiesFile* properties_file_open(const char* path);
|
PropertiesFile* properties_file_open(const char* path);
|
||||||
|
|
||||||
|
|||||||
@ -168,8 +168,19 @@ struct SystemEventSubscription {
|
|||||||
int waiter_count;
|
int waiter_count;
|
||||||
/** Set by system_event_unsubscribe() before it gives `semaphore` and waits, so a task
|
/** Set by system_event_unsubscribe() before it gives `semaphore` and waits, so a task
|
||||||
* already blocked in system_event_await() bails out (ERROR_INVALID_STATE) instead of
|
* already blocked in system_event_await() bails out (ERROR_INVALID_STATE) instead of
|
||||||
* waiting out its full timeout. Reset on the next system_event_subscribe(). */
|
* waiting out its full timeout. Reset once system_event_unsubscribe() finishes
|
||||||
|
* draining old awaiters (see unsubscribe_in_progress) - not simply "on the next
|
||||||
|
* system_event_subscribe()", so a fresh registration can never observe a stale `true`
|
||||||
|
* left over from an unsubscribe that hasn't returned yet. */
|
||||||
bool cancelled;
|
bool cancelled;
|
||||||
|
/** True from the moment system_event_unsubscribe() unlinks `sub` until it has finished
|
||||||
|
* draining old awaiters and deleted the old semaphore. system_event_subscribe() spins
|
||||||
|
* until this clears before reusing `sub` - otherwise a new registration could reset
|
||||||
|
* waiter_count/cancelled (both shared with the old registration, there being only one
|
||||||
|
* `sub`) out from under the old system_event_unsubscribe() call still relying on them,
|
||||||
|
* or hand out a new semaphore for that same call to then promptly delete instead of the
|
||||||
|
* old one, while an old awaiter is still blocked on the real old semaphore. */
|
||||||
|
bool unsubscribe_in_progress;
|
||||||
|
|
||||||
struct SystemEventSubscription* next;
|
struct SystemEventSubscription* next;
|
||||||
} internal;
|
} internal;
|
||||||
@ -178,6 +189,10 @@ struct SystemEventSubscription {
|
|||||||
/**
|
/**
|
||||||
* Register a poll subscription for events of @a sub->type.
|
* Register a poll subscription for events of @a sub->type.
|
||||||
* @warning Does not work in ISR context.
|
* @warning Does not work in ISR context.
|
||||||
|
* @warning If @a sub was just passed to system_event_unsubscribe() (e.g. reusing a node for a
|
||||||
|
* new registration) and that call hasn't returned yet on another task, this call blocks
|
||||||
|
* (briefly - not for the full duration of anyone's timeout) until it does, before registering -
|
||||||
|
* see SystemEventSubscription::internal.unsubscribe_in_progress.
|
||||||
* @param[in,out] sub subscription to register; caller sets @a sub->type beforehand, owns the
|
* @param[in,out] sub subscription to register; caller sets @a sub->type beforehand, owns the
|
||||||
* storage, and must keep it alive (and stationary) until unsubscribed
|
* storage, and must keep it alive (and stationary) until unsubscribed
|
||||||
* @retval ERROR_NONE on success
|
* @retval ERROR_NONE on success
|
||||||
@ -194,6 +209,8 @@ error_t system_event_subscribe(struct SystemEventSubscription* sub);
|
|||||||
* currently blocked in system_event_await() on @a sub has woken up and left, so it's safe to
|
* currently blocked in system_event_await() on @a sub has woken up and left, so it's safe to
|
||||||
* delete the subscription's semaphore before this call returns. A blocked awaiter is woken
|
* delete the subscription's semaphore before this call returns. A blocked awaiter is woken
|
||||||
* (with ERROR_INVALID_STATE) as part of this call rather than left to time out on its own.
|
* (with ERROR_INVALID_STATE) as part of this call rather than left to time out on its own.
|
||||||
|
* A concurrent system_event_subscribe() reusing the same @a sub waits out this same window
|
||||||
|
* (see system_event_subscribe()'s @warning) rather than racing it.
|
||||||
* @param[in] sub subscription to remove, as passed to system_event_subscribe()
|
* @param[in] sub subscription to remove, as passed to system_event_subscribe()
|
||||||
* @return ERROR_NONE on success, ERROR_NOT_FOUND if no matching subscription exists
|
* @return ERROR_NONE on success, ERROR_NOT_FOUND if no matching subscription exists
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -44,17 +44,7 @@ Bundle* bundle_clone(const Bundle* bundle) {
|
|||||||
if (clone == nullptr) {
|
if (clone == nullptr) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
// The Bundle allocation above is nothrow, but copy-assigning `entries` (allocating a node
|
clone->entries = bundle->entries;
|
||||||
// and copying the key/value_string for every entry) is not - std::bad_alloc could still
|
|
||||||
// escape mid-copy. Callers only ever check for a NULL return, so convert that into the
|
|
||||||
// documented nullptr-on-failure contract instead of letting it propagate out of this
|
|
||||||
// extern "C" function (which would be undefined behavior).
|
|
||||||
try {
|
|
||||||
clone->entries = bundle->entries;
|
|
||||||
} catch (...) {
|
|
||||||
delete clone;
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
#include <tactility/filesystem/file_mutex.h>
|
#include <tactility/filesystem/file_mutex.h>
|
||||||
#include <tactility/log.h>
|
#include <tactility/log.h>
|
||||||
|
|
||||||
|
#include <cerrno>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@ -48,9 +49,9 @@ namespace {
|
|||||||
// close(). Mirrors Tactility's loadPropertiesFile(): "#"-prefixed and blank lines are skipped;
|
// close(). Mirrors Tactility's loadPropertiesFile(): "#"-prefixed and blank lines are skipped;
|
||||||
// a "[section]" line becomes a literal prefix (verbatim, brackets included) prepended to every
|
// a "[section]" line becomes a literal prefix (verbatim, brackets included) prepended to every
|
||||||
// subsequent key, until the next "[section]" line replaces it.
|
// subsequent key, until the next "[section]" line replaces it.
|
||||||
// @return false if the file exists but a genuine I/O error interrupted reading it (fgetc()'s
|
// @return false if the file exists but a genuine I/O error interrupted opening or reading it
|
||||||
// EOF return doesn't by itself distinguish clean end-of-file from a read error - ferror() after
|
// (fgetc()'s EOF return doesn't by itself distinguish clean end-of-file from a read error -
|
||||||
// the loop does); true otherwise, including for a missing file.
|
// ferror() after the loop does); true otherwise, including for a missing file (ENOENT).
|
||||||
bool load_from_file(PropertiesFile* file) {
|
bool load_from_file(PropertiesFile* file) {
|
||||||
FileMutex mutex {};
|
FileMutex mutex {};
|
||||||
file_mutex_get(&mutex, file->path.c_str());
|
file_mutex_get(&mutex, file->path.c_str());
|
||||||
@ -58,8 +59,13 @@ bool load_from_file(PropertiesFile* file) {
|
|||||||
|
|
||||||
FILE* handle = std::fopen(file->path.c_str(), "r");
|
FILE* handle = std::fopen(file->path.c_str(), "r");
|
||||||
if (handle == nullptr) {
|
if (handle == nullptr) {
|
||||||
|
const int open_error = errno;
|
||||||
file_mutex_unlock(&mutex);
|
file_mutex_unlock(&mutex);
|
||||||
return true;
|
if (open_error == ENOENT) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
LOG_E(TAG, "Failed to open %s", file->path.c_str());
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string key_prefix;
|
std::string key_prefix;
|
||||||
|
|||||||
@ -166,6 +166,23 @@ error_t system_event_emit(
|
|||||||
}
|
}
|
||||||
|
|
||||||
error_t system_event_subscribe(SystemEventSubscription* sub) {
|
error_t system_event_subscribe(SystemEventSubscription* sub) {
|
||||||
|
// Wait out any system_event_unsubscribe() call still draining old awaiters for this same
|
||||||
|
// `sub` on another task (see internal.unsubscribe_in_progress). waiter_count/cancelled
|
||||||
|
// belong to `sub` itself, not to a given registration - reusing `sub` before that call
|
||||||
|
// finishes would reset them out from under it, and could hand out a fresh semaphore for it
|
||||||
|
// to then promptly delete instead of the old one, while an old awaiter is still blocked on
|
||||||
|
// the real old semaphore.
|
||||||
|
while (true) {
|
||||||
|
mutex_lock(&poll_subscriptions_mutex.handle);
|
||||||
|
bool busy = sub->internal.unsubscribe_in_progress;
|
||||||
|
mutex_unlock(&poll_subscriptions_mutex.handle);
|
||||||
|
|
||||||
|
if (!busy) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
delay_ticks(pdMS_TO_TICKS(10));
|
||||||
|
}
|
||||||
|
|
||||||
SemaphoreHandle_t semaphore = xSemaphoreCreateBinary();
|
SemaphoreHandle_t semaphore = xSemaphoreCreateBinary();
|
||||||
if (semaphore == nullptr) {
|
if (semaphore == nullptr) {
|
||||||
return ERROR_OUT_OF_MEMORY;
|
return ERROR_OUT_OF_MEMORY;
|
||||||
@ -218,6 +235,9 @@ error_t system_event_unsubscribe(SystemEventSubscription* sub) {
|
|||||||
// this point would overwrite that field with a freshly created semaphore, and we must
|
// this point would overwrite that field with a freshly created semaphore, and we must
|
||||||
// not delete the wrong (newly active) one.
|
// not delete the wrong (newly active) one.
|
||||||
sub->internal.cancelled = true;
|
sub->internal.cancelled = true;
|
||||||
|
// Blocks a concurrent system_event_subscribe() from reusing `sub` until this whole
|
||||||
|
// call returns - see internal.unsubscribe_in_progress and system_event_subscribe().
|
||||||
|
sub->internal.unsubscribe_in_progress = true;
|
||||||
semaphore_to_delete = sub->internal.semaphore;
|
semaphore_to_delete = sub->internal.semaphore;
|
||||||
sub->internal.semaphore = nullptr;
|
sub->internal.semaphore = nullptr;
|
||||||
}
|
}
|
||||||
@ -246,9 +266,13 @@ error_t system_event_unsubscribe(SystemEventSubscription* sub) {
|
|||||||
|
|
||||||
vSemaphoreDelete(semaphore_to_delete);
|
vSemaphoreDelete(semaphore_to_delete);
|
||||||
|
|
||||||
// Reset so a future system_event_subscribe() re-registering this same `sub` isn't left
|
// Reset under the lock, together, as the last step - only past this point is `sub` safe
|
||||||
// pre-cancelled (subscribe() also resets this itself, defensively).
|
// for system_event_subscribe() to reuse (see internal.unsubscribe_in_progress and the
|
||||||
|
// busy-wait at the top of system_event_subscribe()).
|
||||||
|
mutex_lock(&poll_subscriptions_mutex.handle);
|
||||||
sub->internal.cancelled = false;
|
sub->internal.cancelled = false;
|
||||||
|
sub->internal.unsubscribe_in_progress = false;
|
||||||
|
mutex_unlock(&poll_subscriptions_mutex.handle);
|
||||||
|
|
||||||
return ERROR_NONE;
|
return ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -231,6 +231,7 @@ TEST_CASE("preferences_open creates missing parent directories (recursively) and
|
|||||||
CHECK(file_exists(nested_path));
|
CHECK(file_exists(nested_path));
|
||||||
|
|
||||||
Preferences* reopened = preferences_open(nested_path);
|
Preferences* reopened = preferences_open(nested_path);
|
||||||
|
REQUIRE_NE(reopened, nullptr);
|
||||||
int32_t out = 0;
|
int32_t out = 0;
|
||||||
CHECK(preferences_opt_int32(reopened, "count", &out));
|
CHECK(preferences_opt_int32(reopened, "count", &out));
|
||||||
CHECK_EQ(out, 7);
|
CHECK_EQ(out, 7);
|
||||||
|
|||||||
@ -193,11 +193,17 @@ TEST_CASE("properties_file_close reports ERROR_RESOURCE when the parent director
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("a failed close leaves previously-saved content on disk untouched") {
|
TEST_CASE("a failed close leaves previously-saved content on disk untouched") {
|
||||||
|
if (geteuid() == 0) {
|
||||||
|
// Root bypasses directory write permissions, so the read-only directory below would
|
||||||
|
// not make save_to_file() fail.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const char* dir = "/tmp/tactility_kernel_properties_file_readonly_test";
|
const char* dir = "/tmp/tactility_kernel_properties_file_readonly_test";
|
||||||
const char* path = "/tmp/tactility_kernel_properties_file_readonly_test/settings.properties";
|
const char* path = "/tmp/tactility_kernel_properties_file_readonly_test/settings.properties";
|
||||||
|
|
||||||
mkdir(dir, 0777);
|
mkdir(dir, 0700);
|
||||||
chmod(dir, 0777);
|
chmod(dir, 0700);
|
||||||
std::remove(path);
|
std::remove(path);
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -214,7 +220,7 @@ TEST_CASE("a failed close leaves previously-saved content on disk untouched") {
|
|||||||
properties_file_set(file, "key", "corrupted");
|
properties_file_set(file, "key", "corrupted");
|
||||||
CHECK_EQ(properties_file_close(file), ERROR_RESOURCE);
|
CHECK_EQ(properties_file_close(file), ERROR_RESOURCE);
|
||||||
|
|
||||||
chmod(dir, 0777); // restore write access for the check below and for cleanup
|
chmod(dir, 0700); // restore write access for the check below and for cleanup
|
||||||
|
|
||||||
PropertiesFile* reloaded = properties_file_open(path);
|
PropertiesFile* reloaded = properties_file_open(path);
|
||||||
char buffer[32];
|
char buffer[32];
|
||||||
|
|||||||
@ -399,7 +399,7 @@ TEST_CASE("system_event_unsubscribe wakes a task blocked in system_event_await w
|
|||||||
CHECK_EQ(system_event_unsubscribe(&sub), ERROR_NONE);
|
CHECK_EQ(system_event_unsubscribe(&sub), ERROR_NONE);
|
||||||
CHECK_LT(get_ticks() - before, pdMS_TO_TICKS(1000));
|
CHECK_LT(get_ticks() - before, pdMS_TO_TICKS(1000));
|
||||||
|
|
||||||
CHECK_EQ(thread_join(thread, 2, 1), ERROR_NONE);
|
CHECK_EQ(thread_join(thread, pdMS_TO_TICKS(2000), pdMS_TO_TICKS(1)), ERROR_NONE);
|
||||||
CHECK_EQ(thread_get_return_code(thread), ERROR_INVALID_STATE);
|
CHECK_EQ(thread_get_return_code(thread), ERROR_INVALID_STATE);
|
||||||
thread_free(thread);
|
thread_free(thread);
|
||||||
|
|
||||||
@ -432,7 +432,7 @@ TEST_CASE("a subscription node can be re-subscribed after system_event_unsubscri
|
|||||||
);
|
);
|
||||||
CHECK_EQ(thread_start(thread), ERROR_NONE);
|
CHECK_EQ(thread_start(thread), ERROR_NONE);
|
||||||
CHECK_EQ(system_event_await(&sub, pdMS_TO_TICKS(2000)), ERROR_NONE);
|
CHECK_EQ(system_event_await(&sub, pdMS_TO_TICKS(2000)), ERROR_NONE);
|
||||||
CHECK_EQ(thread_join(thread, 2, 1), ERROR_NONE);
|
CHECK_EQ(thread_join(thread, pdMS_TO_TICKS(2000), pdMS_TO_TICKS(1)), ERROR_NONE);
|
||||||
thread_free(thread);
|
thread_free(thread);
|
||||||
|
|
||||||
CHECK_EQ(system_event_unsubscribe(&sub), ERROR_NONE);
|
CHECK_EQ(system_event_unsubscribe(&sub), ERROR_NONE);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user