This is an automated email from the ASF dual-hosted git repository.

pnoltes pushed a commit to branch feature/deadlock_on_stop_cmd
in repository https://gitbox.apache.org/repos/asf/celix.git

commit f76b082e6bf98b6b1d8c286aa3e4e460d1165852
Author: Pepijn Noltes <[email protected]>
AuthorDate: Sun Oct 3 13:49:18 2021 +0200

    Adds async install,start,stop and uninstall bundle command for use in the 
shell commands
    
    Bundle lifecycle shell commands need to be async to prevent deadlock issue. 
For example when a `stop` is used to stop the shell bundle.
    
    Also refactor shell_tui and introduces unit test for the shell_tui.
---
 bundles/shell/shell/CMakeLists.txt                 |   2 +-
 bundles/shell/shell/{test => gtest}/CMakeLists.txt |   4 +-
 .../src/ShellTestSuite.cc}                         |  32 ++-
 bundles/shell/shell/src/install_command.c          |  18 +-
 bundles/shell/shell/src/lb_command.c               |  89 +-----
 bundles/shell/shell/src/start_command.c            |   4 +-
 bundles/shell/shell/src/std_commands.c             |  10 +-
 bundles/shell/shell/src/stop_command.c             |   4 +-
 bundles/shell/shell/src/uninstall_command.c        |  11 +-
 bundles/shell/shell_tui/CMakeLists.txt             |  17 +-
 bundles/shell/shell_tui/README.md                  |   2 +-
 bundles/shell/shell_tui/{ => gtest}/CMakeLists.txt |  36 +--
 .../shell/shell_tui/gtest/src/ShellTuiTestSuite.cc | 238 ++++++++++++++++
 bundles/shell/shell_tui/private/src/activator.c    | 126 ---------
 bundles/shell/shell_tui/src/activator.c            | 100 +++++++
 .../shell/shell_tui/{private => }/src/history.c    |   0
 .../shell_tui/{private/include => src}/history.h   |   0
 .../shell/shell_tui/{private => }/src/shell_tui.c  | 305 ++++++++++++---------
 .../shell_tui/{private/include => src}/shell_tui.h |  32 ++-
 libs/framework/gtest/src/single_framework_test.cpp |  21 ++
 libs/framework/include/celix_framework.h           |  84 ++++--
 libs/framework/src/framework.c                     |  51 +++-
 .../src/framework_bundle_lifecycle_handler.c       |  29 +-
 libs/framework/src/framework_private.h             |  30 +-
 24 files changed, 788 insertions(+), 457 deletions(-)

diff --git a/bundles/shell/shell/CMakeLists.txt 
b/bundles/shell/shell/CMakeLists.txt
index 698eba4..432a52c 100644
--- a/bundles/shell/shell/CMakeLists.txt
+++ b/bundles/shell/shell/CMakeLists.txt
@@ -87,6 +87,6 @@ if (SHELL)
        endif ()
 
        if (ENABLE_TESTING)
-               add_subdirectory(test)
+               add_subdirectory(gtest)
        endif()
 endif (SHELL)
diff --git a/bundles/shell/shell/test/CMakeLists.txt 
b/bundles/shell/shell/gtest/CMakeLists.txt
similarity index 93%
rename from bundles/shell/shell/test/CMakeLists.txt
rename to bundles/shell/shell/gtest/CMakeLists.txt
index fdca2de..120c738 100644
--- a/bundles/shell/shell/test/CMakeLists.txt
+++ b/bundles/shell/shell/gtest/CMakeLists.txt
@@ -16,7 +16,7 @@
 # under the License.
 
 add_executable(test_shell
-        src/ShellTestSuite.cpp
+        src/ShellTestSuite.cc
 )
 
 target_link_libraries(test_shell PRIVATE Celix::framework Celix::shell_api 
GTest::gtest GTest::gtest_main)
@@ -32,7 +32,7 @@ setup_target_for_coverage(test_shell SCAN_DIR ..)
 
 
 add_executable(test_cxx_shell
-        src/ShellTestSuite.cpp
+        src/ShellTestSuite.cc
 )
 
 target_link_libraries(test_cxx_shell PRIVATE Celix::framework Celix::shell_api 
GTest::gtest GTest::gtest_main)
diff --git a/bundles/shell/shell/test/src/ShellTestSuite.cpp 
b/bundles/shell/shell/gtest/src/ShellTestSuite.cc
similarity index 90%
rename from bundles/shell/shell/test/src/ShellTestSuite.cpp
rename to bundles/shell/shell/gtest/src/ShellTestSuite.cc
index 6666e11..93d7acc 100644
--- a/bundles/shell/shell/test/src/ShellTestSuite.cpp
+++ b/bundles/shell/shell/gtest/src/ShellTestSuite.cc
@@ -28,7 +28,10 @@ class ShellTestSuite : public ::testing::Test {
 public:
     static constexpr const char * const SHELL_BUNDLE_LOC = "" 
SHELL_BUNDLE_LOCATION "";
 
-    ShellTestSuite() : ctx{createFrameworkContext()} {}
+    ShellTestSuite() : ctx{createFrameworkContext()} {
+        shellBundleId = celix_bundleContext_installBundle(ctx.get(), 
SHELL_BUNDLE_LOCATION, true);
+        EXPECT_GE(shellBundleId, 0);
+    }
 
     static std::shared_ptr<celix_bundle_context_t> createFrameworkContext() {
         auto properties = properties_create();
@@ -39,15 +42,13 @@ public:
         auto* cFw = celix_frameworkFactory_createFramework(properties);
         auto cCtx = framework_getContext(cFw);
 
-        long shellBundleId = celix_bundleContext_installBundle(cCtx, 
SHELL_BUNDLE_LOCATION, true);
-        EXPECT_GE(shellBundleId, 0);
-
         return std::shared_ptr<celix_bundle_context_t>{cCtx, 
[](celix_bundle_context_t* context) {
             auto *fw = celix_bundleContext_getFramework(context);
             celix_frameworkFactory_destroyFramework(fw);
         }};
     }
-    
+
+    long shellBundleId = -1;
     std::shared_ptr<celix_bundle_context_t> ctx;
 };
 
@@ -88,13 +89,13 @@ static void 
callCommand(std::shared_ptr<celix_bundle_context_t>& ctx, const char
 
 TEST_F(ShellTestSuite, testAllCommandsAreCallable) {
     callCommand(ctx, "non-existing", false);
-    callCommand(ctx, "install a-bundle-loc.zip", false);
+    callCommand(ctx, "install a-bundle-loc.zip", true);
     callCommand(ctx, "help", true);
     callCommand(ctx, "help lb", false); //note need namespace
     callCommand(ctx, "help celix::lb", true);
     callCommand(ctx, "help non-existing-command", false);
-    callCommand(ctx, "lb -a", true);
     callCommand(ctx, "lb", true);
+    callCommand(ctx, "lb -l", true);
     callCommand(ctx, "query", true);
     callCommand(ctx, "q -v", true);
     callCommand(ctx, "stop 15", false);
@@ -117,6 +118,23 @@ TEST_F(ShellTestSuite, stopFrameworkTest) {
     std::this_thread::sleep_for(std::chrono::milliseconds{100});
 }
 
+TEST_F(ShellTestSuite, stopSelfTest) {
+    auto* list = celix_bundleContext_listBundles(ctx.get());
+    EXPECT_EQ(1, celix_arrayList_size(list));
+    celix_arrayList_destroy(list);
+
+    //rule it should be possible to stop the Shell bundle from the stop 
command (which is part of the Shell bundle)
+    std::string cmd = std::string{"stop "} + std::to_string(shellBundleId);
+    callCommand(ctx, cmd.c_str(), true);
+
+    //ensure that the command can be executed
+    std::this_thread::sleep_for(std::chrono::milliseconds{100});
+
+    list = celix_bundleContext_listBundles(ctx.get());
+    EXPECT_EQ(0, celix_arrayList_size(list));
+    celix_arrayList_destroy(list);
+}
+
 TEST_F(ShellTestSuite, queryTest) {
     celix_service_use_options_t opts{};
     opts.filter.serviceName = CELIX_SHELL_COMMAND_SERVICE_NAME;
diff --git a/bundles/shell/shell/src/install_command.c 
b/bundles/shell/shell/src/install_command.c
index 322afc0..0dd5fca 100644
--- a/bundles/shell/shell/src/install_command.c
+++ b/bundles/shell/shell/src/install_command.c
@@ -34,24 +34,22 @@ bool installCommand_execute(void *handle, const char 
*const_line, FILE *outStrea
        sub = strtok_r(line, delims, &save_ptr);
        sub = strtok_r(NULL, delims, &save_ptr);
 
-       bool installSucceeded = false;
-       
        if (sub == NULL) {
                fprintf(errStream, "Incorrect number of arguments.\n");
        } else {
                while (sub != NULL) {
-                   long bndId = celix_bundleContext_installBundle(ctx, sub, 
true);
-                       if (bndId >= 0) {
-                           char *name = 
celix_bundleContext_getBundleSymbolicName(ctx, bndId);
-                fprintf(outStream, "Bundle '%s' installed with bundle id 
%li\n", name, bndId);
-                free(name);
-                installSucceeded = true;
-                       }
+            celix_framework_t* fw = celix_bundleContext_getFramework(ctx);
+            long bndId = celix_framework_installBundleAsync(fw, sub, true);
+            if (bndId >= 0) {
+                fprintf(outStream, "Bundle installed with bundle id %li\n", 
bndId);
+            } else {
+                fprintf(errStream, "Error installed bundle\n");
+            }
                        sub = strtok_r(NULL, delims, &save_ptr);
                }
        }
 
        free(line);
 
-       return installSucceeded;
+       return true;
 }
\ No newline at end of file
diff --git a/bundles/shell/shell/src/lb_command.c 
b/bundles/shell/shell/src/lb_command.c
index e12cb30..902b67e 100644
--- a/bundles/shell/shell/src/lb_command.c
+++ b/bundles/shell/shell/src/lb_command.c
@@ -48,83 +48,10 @@ typedef struct lb_options {
 
     //group
     char *listGroup;
-    bool listAllGroups;
 } lb_options_t;
 
 static char * psCommand_stateString(bundle_state_e state);
 
-static void addToGroup(hash_map_t *map, const char *group, long bndId) {
-    celix_array_list_t *ids = hashMap_get(map, group);
-    if (ids == NULL) {
-        ids = celix_arrayList_create();
-        char *key = strndup(group, 1024 * 1024);
-        hashMap_put(map, key, ids);
-    }
-    celix_arrayList_addLong(ids, bndId);
-}
-
-static void collectGroups(void *handle, const celix_bundle_t *bnd) {
-    hash_map_t *map = handle;
-    const char *group = celix_bundle_getGroup(bnd);
-    if (group == NULL) {
-        group = "-";
-        addToGroup(map, group, celix_bundle_getId(bnd));
-    } else {
-        char *at = strstr(group, "/");
-        if (at != NULL) {
-            unsigned long size = at-group;
-            char buf[size+1];
-            strncpy(buf, group, size);
-            buf[size] = '\0';
-            addToGroup(map, buf, celix_bundle_getId(bnd));
-        } else {
-            addToGroup(map, group, celix_bundle_getId(bnd));
-        }
-    }
-}
-
-static void lbCommand_showGroups(celix_bundle_context_t *ctx, const 
lb_options_t *opts, FILE *out) {
-    const char* startColor = "";
-    const char* endColor = "";
-    if (opts->useColors) {
-        startColor = HEAD_COLOR;
-        endColor = END_COLOR;
-    }
-
-    fprintf(out, "%s  Groups:%s\n", startColor, endColor);
-    fprintf(out, "%s  %-20s %-20s %s\n", startColor, "Group", "Bundle Ids", 
endColor);
-
-    hash_map_t *map = hashMap_create(utils_stringHash, NULL, 
utils_stringEquals, NULL);
-    celix_bundleContext_useBundle(ctx, 0, map, collectGroups);
-    celix_bundleContext_useBundles(ctx, map, collectGroups);
-
-    hash_map_iterator_t iter = hashMapIterator_construct(map);
-    int count = 0;
-    while (hashMapIterator_hasNext(&iter)) {
-        startColor = "";
-        endColor = "";
-        if (opts->useColors) {
-            startColor = count++ % 2 == 0 ? EVEN_COLOR : ODD_COLOR;
-            endColor = END_COLOR;
-        }
-
-        hash_map_entry_t *entry = hashMapIterator_nextEntry(&iter);
-        char *key = hashMapEntry_getKey(entry);
-        fprintf(out, "%s  %-20s ", startColor, key);
-        celix_array_list_t *ids = hashMapEntry_getValue(entry);
-        int s = celix_arrayList_size(ids);
-        for (int i = s-1; i >= 0; --i) { //note reverse to start with lower 
bundle id first
-            long id = celix_arrayList_getLong(ids, (int)i);
-            fprintf(out, "%li ", id);
-        }
-        fprintf(out, "%s\n", endColor);
-        free(key);
-        celix_arrayList_destroy(ids);
-    }
-    fprintf(out, "\n\n");
-    hashMap_destroy(map, false, false);
-}
-
 static void lbCommand_listBundles(celix_bundle_context_t *ctx, const 
lb_options_t *opts, FILE *out) {
     const char *message_str = "Name";
     if (opts->show_location) {
@@ -248,14 +175,9 @@ static void lbCommand_listBundles(celix_bundle_context_t 
*ctx, const lb_options_
                 startColor = i % 2 == 0 ? EVEN_COLOR : ODD_COLOR;
                 endColor = END_COLOR;
             }
-            bool print = false;
-            if (opts->listAllGroups) {
-                print = true;
-            } else if (opts->listGroup != NULL && group_str != NULL) {
-                print = strncmp(opts->listGroup, group_str, 
strlen(opts->listGroup)) == 0;
-            } else if (opts->listGroup == NULL){
-                //listGroup == NULL -> only print not grouped bundles
-                print = group_str == NULL;
+            bool print = true;
+            if (opts->listGroup != NULL) {
+                print = group_str != NULL && strstr(group_str, 
opts->listGroup) != NULL;
             }
 
             if (print) {
@@ -302,15 +224,14 @@ bool lbCommand_execute(void *handle, const char 
*const_command_line_str, FILE *o
             opts.show_symbolic_name = true;
         } else if (strcmp(sub_str, "-u") == 0) {
             opts.show_update_location = true;
-        } else if (strcmp(sub_str, "-a") == 0) {
-            opts.listAllGroups = true;
+        } else if (strncmp(sub_str, "-", 1) == 0) {
+            fprintf(out_ptr, "Ignoring unknown lb option '%s'\n", sub_str);
         } else {
             opts.listGroup = strdup(sub_str);
         }
         sub_str = strtok_r(NULL, OSGI_SHELL_COMMAND_SEPARATOR, &save_ptr);
     }
 
-    lbCommand_showGroups(ctx, &opts, out_ptr);
     lbCommand_listBundles(ctx, &opts, out_ptr);
 
     free(opts.listGroup);
diff --git a/bundles/shell/shell/src/start_command.c 
b/bundles/shell/shell/src/start_command.c
index d1fe45b..c97be68 100644
--- a/bundles/shell/shell/src/start_command.c
+++ b/bundles/shell/shell/src/start_command.c
@@ -46,7 +46,9 @@ bool startCommand_execute(void *handle, const char 
*const_command, FILE *outStre
             } else {
                 bool exists = celix_bundleContext_isBundleInstalled(ctx, 
bndId);
                 if (exists) {
-                    startSucceeded = celix_bundleContext_startBundle(ctx, 
bndId);
+                    celix_framework_t* fw = 
celix_bundleContext_getFramework(ctx);
+                    celix_framework_startBundleAsync(fw, bndId);
+                    startSucceeded = true;
                 } else {
                     fprintf(outStream, "No bundle with id %li.\n", bndId);
                 }
diff --git a/bundles/shell/shell/src/std_commands.c 
b/bundles/shell/shell/src/std_commands.c
index 8ad0e6c..e41dd07 100644
--- a/bundles/shell/shell/src/std_commands.c
+++ b/bundles/shell/shell/src/std_commands.c
@@ -51,10 +51,10 @@ celix_std_commands_t* 
celix_stdCommands_create(celix_bundle_context_t* ctx) {
             (struct celix_shell_command_register_entry) {
                     .exec = lbCommand_execute,
                     .name = "celix::lb",
-                    .description = "list bundles. Default only the groupless 
bundles are listed. Use -a to list all bundles." \
-                            "\nIf a group string is provided only bundles 
matching the group string will be listed." \
+                    .description = "list bundles. Default all bundles are 
listed." \
+                            "\nIf a group string is provided only bundles 
where the bundle group matching group string will be listed." \
                             "\nUse -l to print the bundle locations.\nUse -s 
to print the bundle symbolic names\nUse -u to print the bundle update 
location.",
-                    .usage = "lb [-l | -s | -u | -a] [group]"
+                    .usage = "lb [-l | -s | -u] [group]"
             };
     commands->std_commands[1] =
             (struct celix_shell_command_register_entry) {
@@ -95,8 +95,8 @@ celix_std_commands_t* 
celix_stdCommands_create(celix_bundle_context_t* ctx) {
             (struct celix_shell_command_register_entry) {
                     .exec = helpCommand_execute,
                     .name = "celix::help",
-                    .description = "display available commands and 
description.",
-                    .usage = "help <command>]"
+                    .description = "display available commands or detail info 
if a command argument is provided",
+                    .usage = "help [<command>]"
             };
     commands->std_commands[7] =
             (struct celix_shell_command_register_entry) {
diff --git a/bundles/shell/shell/src/stop_command.c 
b/bundles/shell/shell/src/stop_command.c
index 340add7..71c3b58 100644
--- a/bundles/shell/shell/src/stop_command.c
+++ b/bundles/shell/shell/src/stop_command.c
@@ -46,7 +46,9 @@ bool stopCommand_execute(void *handle, const char 
*const_command, FILE *outStrea
             } else {
                 bool exists = celix_bundleContext_isBundleInstalled(ctx, 
bndId);
                 if (exists) {
-                    stoppedCalledAndSucceeded = 
celix_bundleContext_stopBundle(ctx, bndId);
+                    celix_framework_t* fw = 
celix_bundleContext_getFramework(ctx);
+                    celix_framework_stopBundleAsync(fw, bndId);
+                    stoppedCalledAndSucceeded = true;
                 } else {
                     fprintf(outStream, "No bundle with id %li.\n", bndId);
                 }
diff --git a/bundles/shell/shell/src/uninstall_command.c 
b/bundles/shell/shell/src/uninstall_command.c
index c2e398a..8c92e5f 100644
--- a/bundles/shell/shell/src/uninstall_command.c
+++ b/bundles/shell/shell/src/uninstall_command.c
@@ -16,13 +16,6 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/**
- * uninstall_command.c
- *
- *  \date       Aug 20, 2010
- *  \author            <a href="mailto:[email protected]";>Apache Celix 
Project Team</a>
- *  \copyright Apache License, Version 2.0
- */
 
 #include <stdlib.h>
 #include <string.h>
@@ -49,7 +42,9 @@ bool uninstallCommand_execute(void *handle, const char* 
const_command, FILE *out
                        long bndId = atol(sub);
                        bool exists = 
celix_bundleContext_isBundleInstalled(ctx, bndId);
                        if (exists) {
-                uninstallSucceeded = celix_bundleContext_uninstallBundle(ctx, 
bndId);
+                celix_framework_t* fw = celix_bundleContext_getFramework(ctx);
+                celix_framework_uninstallBundleAsync(fw, bndId);
+                uninstallSucceeded = true;
                        } else {
                 fprintf(outStream, "No bundle with id %li.\n", bndId);
             }
diff --git a/bundles/shell/shell_tui/CMakeLists.txt 
b/bundles/shell/shell_tui/CMakeLists.txt
index 31bb87a..1002a77 100644
--- a/bundles/shell/shell_tui/CMakeLists.txt
+++ b/bundles/shell/shell_tui/CMakeLists.txt
@@ -24,19 +24,20 @@ if (SHELL_TUI)
                FILENAME celix_shell_tui
                GROUP "Celix/Shell"
        SOURCES 
-               private/src/activator.c
-               private/src/shell_tui.c
-               private/src/history.c
+               src/activator.c
+               src/shell_tui.c
+               src/history.c
        )
        
-       target_include_directories(shell_tui PRIVATE
-                       "${PROJECT_SOURCE_DIR}/utils/public/include"
-                       private/include
-       )
-    target_link_libraries(shell_tui PRIVATE Celix::shell_api)
+       target_include_directories(shell_tui PRIVATE src)
+    target_link_libraries(shell_tui PRIVATE Celix::shell_api Celix::utils)
 
        install_celix_bundle(shell_tui EXPORT celix)
 
        #Alias setup to match external usage
        add_library(Celix::shell_tui ALIAS shell_tui)
+
+       if (ENABLE_TESTING AND TARGET Celix::shell)
+               add_subdirectory(gtest)
+       endif()
 endif (SHELL_TUI)
diff --git a/bundles/shell/shell_tui/README.md 
b/bundles/shell/shell_tui/README.md
index 3027c90..60f10a7 100644
--- a/bundles/shell/shell_tui/README.md
+++ b/bundles/shell/shell_tui/README.md
@@ -28,7 +28,7 @@ The Celix Shell TUI implements a textual user interface for 
the Celix Shell.
 
 ## Config options
 
-- SHELL_USE_ANSI_CONTROL_SEQUENCES - Whether to use ANSI control
+- SHELL_TUI_USE_ANSI_CONTROL_SEQUENCES - Whether to use ANSI control
 sequences to support backspace, left, up, etc key commands in the
 shell tui. Default is true if a TERM environment is set else false.
 
diff --git a/bundles/shell/shell_tui/CMakeLists.txt 
b/bundles/shell/shell_tui/gtest/CMakeLists.txt
similarity index 50%
copy from bundles/shell/shell_tui/CMakeLists.txt
copy to bundles/shell/shell_tui/gtest/CMakeLists.txt
index 31bb87a..76f201c 100644
--- a/bundles/shell/shell_tui/CMakeLists.txt
+++ b/bundles/shell/shell_tui/gtest/CMakeLists.txt
@@ -5,38 +5,24 @@
 # to you under the Apache License, Version 2.0 (the
 # "License"); you may not use this file except in compliance
 # with the License.  You may obtain a copy of the License at
-# 
+#
 #   http://www.apache.org/licenses/LICENSE-2.0
-# 
+#
 # Unless required by applicable law or agreed to in writing,
 # software distributed under the License is distributed on an
 # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-celix_subproject(SHELL_TUI "Option to enable building the Shell Textual User 
Interface bundles" ON DEPS LAUNCHER SHELL)
-if (SHELL_TUI)
 
-    add_celix_bundle(shell_tui
-       SYMBOLIC_NAME "apache_celix_shell_tui"
-       VERSION "1.1.0"
-       NAME "Apache Celix Shell TUI"
-               FILENAME celix_shell_tui
-               GROUP "Celix/Shell"
-       SOURCES 
-               private/src/activator.c
-               private/src/shell_tui.c
-               private/src/history.c
-       )
-       
-       target_include_directories(shell_tui PRIVATE
-                       "${PROJECT_SOURCE_DIR}/utils/public/include"
-                       private/include
-       )
-    target_link_libraries(shell_tui PRIVATE Celix::shell_api)
+add_executable(test_shell_tui
+        src/ShellTuiTestSuite.cc
+)
 
-       install_celix_bundle(shell_tui EXPORT celix)
+target_link_libraries(test_shell_tui PRIVATE Celix::framework GTest::gtest 
GTest::gtest_main)
+add_celix_bundle_dependencies(test_shell_tui Celix::shell Celix::shell_tui)
+target_compile_definitions(test_shell_tui PRIVATE 
-DSHELL_BUNDLE_LOCATION=\"$<TARGET_PROPERTY:shell,BUNDLE_FILE>\")
+target_compile_definitions(test_shell_tui PRIVATE 
-DSHELL_TUI_BUNDLE_LOCATION=\"$<TARGET_PROPERTY:shell_tui,BUNDLE_FILE>\")
 
-       #Alias setup to match external usage
-       add_library(Celix::shell_tui ALIAS shell_tui)
-endif (SHELL_TUI)
+add_test(NAME test_shell_tui COMMAND test_shell_tui)
+setup_target_for_coverage(test_shell_tui SCAN_DIR ..)
\ No newline at end of file
diff --git a/bundles/shell/shell_tui/gtest/src/ShellTuiTestSuite.cc 
b/bundles/shell/shell_tui/gtest/src/ShellTuiTestSuite.cc
new file mode 100644
index 0000000..ce129ed
--- /dev/null
+++ b/bundles/shell/shell_tui/gtest/src/ShellTuiTestSuite.cc
@@ -0,0 +1,238 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <gtest/gtest.h>
+#include <fcntl.h>
+
+#include "celix/FrameworkFactory.h"
+#include "celix/BundleContext.h"
+
+class ShellTuiTestSuite : public ::testing::Test {
+public:
+    ShellTuiTestSuite() {
+        //open pipe to stimulate shell tui input
+        int fds[2];
+        int rc = pipe(fds);
+        EXPECT_EQ(rc, 0) << strerror(errno);
+        if (rc == 0) {
+            inputReadFd = fds[0];
+            inputWriteFd = fds[1];
+        }
+
+        //open pipe to stimulate shell tui output
+        rc = pipe(fds);
+        EXPECT_EQ(rc, 0) << strerror(errno);
+        if (rc == 0) {
+            outputReadFd = fds[0];
+            outputWriteFd = fds[1];
+
+            //set readFd non blocking
+            int flags = fcntl(outputReadFd, F_GETFL, 0);
+            fcntl(outputReadFd, F_SETFL, flags | O_NONBLOCK);
+        }
+    }
+
+    ~ShellTuiTestSuite() override {
+        close(inputReadFd);
+        close(inputWriteFd);
+        close(outputReadFd);
+        close(outputWriteFd);
+    }
+
+    void createFrameworkWithShellBundles(celix::Properties config = {}, bool 
configurePipes = false, bool installShell = true, bool installShellTui = true) {
+        config.set("CELIX_LOGGING_DEFAULT_ACTIVE_LOG_LEVEL", "trace");
+
+        if (configurePipes) {
+            config.set("SHELL_TUI_INPUT_FILE_DESCRIPTOR", 
std::to_string(inputReadFd).c_str());
+            config.set("SHELL_TUI_OUTPUT_FILE_DESCRIPTOR", 
std::to_string(outputWriteFd).c_str());
+            config.set("SHELL_TUI_ERROR_FILE_DESCRIPTOR", 
std::to_string(outputWriteFd).c_str());
+        }
+
+        fw = celix::createFramework(config);
+        ctx = fw->getFrameworkBundleContext();
+
+        if (installShell) {
+            shellBundleId = ctx->installBundle(SHELL_BUNDLE_LOCATION);
+            EXPECT_GT(shellBundleId, 0);
+        }
+        if (installShellTui) {
+            shellTuiBundleId = ctx->installBundle(SHELL_TUI_BUNDLE_LOCATION);
+            EXPECT_GT(shellTuiBundleId, 0);
+        }
+    }
+
+    [[nodiscard]] std::string readPipeOutput() const {
+        constexpr int BUFSIZE = 1024 * 10;
+        char buf[BUFSIZE];
+        buf[BUFSIZE-1] = '\0'; //ensure 0 terminated
+        auto bytesRead = read(outputReadFd, buf, BUFSIZE-1);
+        EXPECT_GT(bytesRead, 0);
+        return std::string{buf};
+    }
+
+    void writeCmd(const char* cmd) const {
+        write(inputWriteFd, cmd, strlen(cmd)+1);
+        std::this_thread::sleep_for(std::chrono::milliseconds{100}); //sleep 
to let command be handled.
+    }
+
+    void testExecuteLb(bool enableAnsiControlSequence) {
+        celix::Properties config{
+                {"SHELL_TUI_USE_ANSI_CONTROL_SEQUENCES", 
enableAnsiControlSequence ? "true" : "false"},
+
+        };
+        createFrameworkWithShellBundles(std::move(config), true);
+
+        const char* cmd = "lb\n";
+        writeCmd(cmd);
+
+        auto lbResult = readPipeOutput(); //lb output
+        std::cout << lbResult << std::endl;
+        EXPECT_TRUE(strstr(lbResult.c_str(), "Apache Celix Shell TUI")); //lb 
should print the shell tui name.
+    }
+
+    void testExecuteLbWithoutShell(bool enableAnsiControlSequence) {
+        celix::Properties config{
+                {"SHELL_TUI_USE_ANSI_CONTROL_SEQUENCES", 
enableAnsiControlSequence ? "true" : "false"}
+        };
+        createFrameworkWithShellBundles(std::move(config), true, false);
+
+        const char* cmd = "lb\n";
+        writeCmd(cmd);
+
+        auto lbResult = readPipeOutput(); //lb output
+        std::cout << lbResult << std::endl;
+        EXPECT_TRUE(strstr(lbResult.c_str(), "[Shell TUI] Shell service not 
available") != nullptr);
+    }
+
+    void testAutoCompleteForCommand(const char* cmd, const char* 
expectedOutputPart) {
+        //Test the triggering of auto complete and if the output contains the 
expected part
+        celix::Properties config{
+                {"SHELL_TUI_USE_ANSI_CONTROL_SEQUENCES", "true"},
+        };
+        createFrameworkWithShellBundles(std::move(config), true);
+
+        writeCmd(cmd);
+
+        auto result = readPipeOutput();
+        std::cout << result << std::endl;
+        EXPECT_TRUE(strstr(result.c_str(), expectedOutputPart) != nullptr);
+    }
+
+    std::shared_ptr<celix::Framework> fw{};
+    std::shared_ptr<celix::BundleContext> ctx{};
+    long shellBundleId = -1;
+    long shellTuiBundleId = -1;
+    int inputReadFd = -1;
+    int inputWriteFd = -1;
+    int outputReadFd = -1;
+    int outputWriteFd = -1;
+};
+
+TEST_F(ShellTuiTestSuite, testStartStop) {
+    //not empty, but should not leak
+    createFrameworkWithShellBundles();
+}
+
+TEST_F(ShellTuiTestSuite, testExecuteLb) {
+    testExecuteLb(false);
+}
+
+TEST_F(ShellTuiTestSuite, testExecuteLbWithAnsiControlEnabled) {
+    testExecuteLb(true);
+}
+
+TEST_F(ShellTuiTestSuite, testAutoCompleteHelpCommand) {
+    //note incomplete command with a tab -> should complete command to `help`
+    testAutoCompleteForCommand("hel\t", "help");
+}
+
+TEST_F(ShellTuiTestSuite, testAutoCompleteCelixLbCommand) {
+    //note incomplete command with a tab -> should complete command to 
`celix::help`
+    testAutoCompleteForCommand("celix::hel\t", "celix::help");
+}
+
+TEST_F(ShellTuiTestSuite, testAutoCompleteLbUsageCommand) {
+    //note complete help command with a tab -> should print usage
+    testAutoCompleteForCommand("help \t", "Usage:");
+}
+
+TEST_F(ShellTuiTestSuite, testAutoCompleteCelixLbUsageCommand) {
+    //note complete celix::help command with a tab -> should print usage
+    testAutoCompleteForCommand("celix::help \t", "Usage:");
+}
+
+
+TEST_F(ShellTuiTestSuite, testShellTuiWithInvalidFD) {
+    celix::Properties config{
+            {"SHELL_TUI_USE_ANSI_CONTROL_SEQUENCES", "true"},
+            {"SHELL_TUI_INPUT_FILE_DESCRIPTOR", "555"}, //note invalid fd
+            {"SHELL_TUI_OUTPUT_FILE_DESCRIPTOR", "556"}, //note invalid fd
+            {"SHELL_TUI_ERROR_FILE_DESCRIPTOR", "557"} //note invalid fd
+    };
+    createFrameworkWithShellBundles(std::move(config));
+
+    writeCmd("lb\n");
+}
+
+TEST_F(ShellTuiTestSuite, testShellTuiWithoutShell) {
+    testExecuteLbWithoutShell(false);
+}
+
+TEST_F(ShellTuiTestSuite, testShellTuiWithAnsiControlWithoutShell) {
+    testExecuteLbWithoutShell(true);
+}
+
+TEST_F(ShellTuiTestSuite, testAnsiControl) {
+    celix::Properties config{
+            {"SHELL_TUI_USE_ANSI_CONTROL_SEQUENCES", "true"}
+    };
+    createFrameworkWithShellBundles(std::move(config), true);
+
+    //this test triggers the handling of ansi control sequences, but currently 
does not test the output
+
+    //build history
+    const char* cmd = "lb\n";
+    writeCmd(cmd);
+
+    cmd = "\033[A"; //up
+    writeCmd(cmd);
+
+    cmd = "\033[C"; //right
+    writeCmd(cmd);
+
+    cmd = "\033[D"; //left
+    writeCmd(cmd);
+
+    cmd = "\033[3"; //del1
+    writeCmd(cmd);
+
+    cmd = "\033[~"; //del2
+    writeCmd(cmd);
+
+    cmd = "\033[9"; //tab
+    writeCmd(cmd);
+
+    cmd = "\033[127"; //backspace
+    writeCmd(cmd);
+
+    cmd = "\033[B"; //down
+    writeCmd(cmd);
+
+    std::cout << readPipeOutput() << std::endl;
+}
\ No newline at end of file
diff --git a/bundles/shell/shell_tui/private/src/activator.c 
b/bundles/shell/shell_tui/private/src/activator.c
deleted file mode 100644
index 9dc4da5..0000000
--- a/bundles/shell/shell_tui/private/src/activator.c
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-#include <stdlib.h>
-#include <string.h>
-#include <zconf.h>
-#include <stdio.h>
-#include <unistd.h>
-
-#include "bundle_context.h"
-#include "bundle_activator.h"
-
-
-#include "shell_tui.h"
-#include "celix_shell.h"
-#include "service_tracker.h"
-
-#define SHELL_USE_ANSI_CONTROL_SEQUENCES "SHELL_USE_ANSI_CONTROL_SEQUENCES"
-
-typedef struct shell_tui_activator {
-    shell_tui_t* shellTui;
-    long trackerId;
-    celix_shell_t* currentSvc;
-    bool useAnsiControlSequences;
-} shell_tui_activator_t;
-
-
-celix_status_t bundleActivator_create(bundle_context_pt context, void 
**userData) {
-       celix_status_t status = CELIX_SUCCESS;
-
-       //Check if tty exists
-       if (!isatty(fileno(stdin))) {
-        printf("[Shell TUI] no tty connected. Shell TUI will not activate.\n");
-        return status;
-       }
-
-    shell_tui_activator_t* activator = calloc(1, sizeof(*activator));
-
-       if (activator != NULL) {
-        activator->trackerId = -1L;
-        bool useCommands;
-        const char* config = NULL;
-        bundleContext_getProperty(context, SHELL_USE_ANSI_CONTROL_SEQUENCES, 
&config);
-        if (config != NULL) {
-            useCommands = strncmp("true", config, 5) == 0;
-        } else {
-            char *term = getenv("TERM");
-            useCommands = term != NULL;
-        }
-
-        activator->shellTui = shellTui_create(useCommands);
-
-        {
-            celix_service_tracking_options_t opts = 
CELIX_EMPTY_SERVICE_TRACKING_OPTIONS;
-            opts.filter.serviceName = CELIX_SHELL_SERVICE_NAME;
-            opts.callbackHandle = activator->shellTui;
-            opts.set = (void*)shellTui_setShell;
-
-            activator->trackerId = 
celix_bundleContext_trackServicesWithOptions(context, &opts);
-        }
-       }
-
-    if (activator != NULL && activator->shellTui != NULL) {
-        (*userData) = activator;
-    } else {
-        if (activator != NULL) {
-            shellTui_destroy(activator->shellTui);
-            celix_bundleContext_stopTracker(context, activator->trackerId);
-            activator->trackerId = -1L;
-        }
-        free(activator);
-               status = CELIX_ENOMEM;
-       }
-
-       return status;
-}
-
-celix_status_t bundleActivator_start(void * userData, bundle_context_pt 
context) {
-       celix_status_t status = CELIX_SUCCESS;
-    shell_tui_activator_t* act = (shell_tui_activator_t*) userData;
-
-    if (act != NULL) {
-        shellTui_start(act->shellTui);
-    }
-
-       return status;
-}
-
-celix_status_t bundleActivator_stop(void * userData, bundle_context_pt 
context) {
-       celix_status_t status = CELIX_SUCCESS;
-    shell_tui_activator_t* act = (shell_tui_activator_t*) userData;
-
-    if (act != NULL) {
-        celix_bundleContext_stopTracker(context, act->trackerId);
-        shellTui_stop(act->shellTui);
-    }
-
-       return status;
-}
-
-celix_status_t bundleActivator_destroy(void * userData, bundle_context_pt 
context) {
-    shell_tui_activator_t* act = (shell_tui_activator_t*) userData;
-
-    if (act != NULL) {
-        shellTui_destroy(act->shellTui);
-        free(act);
-    }
-
-       return CELIX_SUCCESS;
-}
diff --git a/bundles/shell/shell_tui/src/activator.c 
b/bundles/shell/shell_tui/src/activator.c
new file mode 100644
index 0000000..aff3682
--- /dev/null
+++ b/bundles/shell/shell_tui/src/activator.c
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <unistd.h>
+#include "celix_bundle_activator.h"
+#include "celix_shell.h"
+#include "shell_tui.h"
+
+/**
+ * Whether to use ANSI control sequences to support backspace, left, up, etc 
key commands in the
+ * shell tui. Default is true if a TERM environment is set else false.
+ */
+#define SHELL_TUI_USE_ANSI_CONTROL_SEQUENCES 
"SHELL_TUI_USE_ANSI_CONTROL_SEQUENCES"
+
+/**
+ * The file descriptor to use as input. Default is `STDIN_FILENO`.
+ * This is used for testing.
+ */
+#define SHELL_TUI_INPUT_FILE_DESCRIPTOR "SHELL_TUI_INPUT_FILE_DESCRIPTOR"
+
+/**
+ * The file descriptor to use as output. Default is `STDOUT_FILENO`.
+ * This is used for testing.
+ */
+#define SHELL_TUI_OUTPUT_FILE_DESCRIPTOR "SHELL_TUI_OUTPUT_FILE_DESCRIPTOR"
+
+/**
+ * The file descriptor to use as error output. Default is `STDERR_FILENO`.
+ * This is used for testing.
+ */
+#define SHELL_TUI_ERROR_FILE_DESCRIPTOR "SHELL_TUI_ERROR_FILE_DESCRIPTOR"
+
+typedef struct celix_shell_tui_activator {
+    shell_tui_t* shellTui;
+    long trackerId;
+} celix_shell_tui_activator_t;
+
+
+static celix_status_t 
celix_shellTuiActivator_start(celix_shell_tui_activator_t* act, 
celix_bundle_context_t* ctx) {
+    celix_status_t status = CELIX_SUCCESS;
+    act->trackerId = -1L;
+
+    int inputFd = (int)celix_bundleContext_getPropertyAsLong(ctx, 
SHELL_TUI_INPUT_FILE_DESCRIPTOR, STDIN_FILENO);
+    int outputFd = (int)celix_bundleContext_getPropertyAsLong(ctx, 
SHELL_TUI_OUTPUT_FILE_DESCRIPTOR, STDOUT_FILENO);
+    int errorFd = (int)celix_bundleContext_getPropertyAsLong(ctx, 
SHELL_TUI_ERROR_FILE_DESCRIPTOR, STDERR_FILENO);
+
+    //Check if tty exists, no tty -> no shell tui expect if 
activateWithoutTTY==true
+    if (inputFd == STDIN_FILENO && !isatty(STDIN_FILENO)) {
+        celix_bundleContext_log(ctx, CELIX_LOG_LEVEL_INFO, "[Shell TUI] no tty 
connected. Shell TUI will not activate.");
+        return status;
+    }
+    bool useCommands = false;
+    char *term = getenv("TERM");
+    useCommands = term != NULL; //if TERM exist, default is to use commands
+    useCommands = celix_bundleContext_getPropertyAsBool(ctx, 
SHELL_TUI_USE_ANSI_CONTROL_SEQUENCES, useCommands);
+    act->shellTui = shellTui_create(useCommands, inputFd, outputFd, errorFd);
+
+    {
+        celix_service_tracking_options_t opts = 
CELIX_EMPTY_SERVICE_TRACKING_OPTIONS;
+        opts.filter.serviceName = CELIX_SHELL_SERVICE_NAME;
+        opts.callbackHandle = act->shellTui;
+        opts.set = (void*)shellTui_setShell;
+        act->trackerId = celix_bundleContext_trackServicesWithOptions(ctx, 
&opts);
+    }
+
+    status = shellTui_start(act->shellTui);
+    if (status != CELIX_SUCCESS) {
+        shellTui_destroy(act->shellTui);
+        act->shellTui = NULL;
+    }
+
+    return status;
+}
+
+static celix_status_t 
celix_shellTuiActivator_stop(celix_shell_tui_activator_t* act, 
celix_bundle_context_t* ctx) {
+    celix_bundleContext_stopTracker(ctx, act->trackerId);
+    if (act->shellTui != NULL) {
+        shellTui_stop(act->shellTui);
+        shellTui_destroy(act->shellTui);
+    }
+    return CELIX_SUCCESS;
+}
+
+CELIX_GEN_BUNDLE_ACTIVATOR(celix_shell_tui_activator_t, 
celix_shellTuiActivator_start, celix_shellTuiActivator_stop)
\ No newline at end of file
diff --git a/bundles/shell/shell_tui/private/src/history.c 
b/bundles/shell/shell_tui/src/history.c
similarity index 100%
rename from bundles/shell/shell_tui/private/src/history.c
rename to bundles/shell/shell_tui/src/history.c
diff --git a/bundles/shell/shell_tui/private/include/history.h 
b/bundles/shell/shell_tui/src/history.h
similarity index 100%
rename from bundles/shell/shell_tui/private/include/history.h
rename to bundles/shell/shell_tui/src/history.h
diff --git a/bundles/shell/shell_tui/private/src/shell_tui.c 
b/bundles/shell/shell_tui/src/shell_tui.c
similarity index 55%
rename from bundles/shell/shell_tui/private/src/shell_tui.c
rename to bundles/shell/shell_tui/src/shell_tui.c
index 33767f7..3b5c658 100644
--- a/bundles/shell/shell_tui/private/src/shell_tui.c
+++ b/bundles/shell/shell_tui/src/shell_tui.c
@@ -16,23 +16,15 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/**
- * shell_tui.c
- *
- *  \date       Aug 13, 2010
- *  \author            <a href="mailto:[email protected]";>Apache Celix 
Project Team</a>
- *  \copyright Apache License, Version 2.0
- */
 
-#include <sys/time.h>
-#include <sys/select.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <termios.h>
 #include <unistd.h>
+#include <poll.h>
 
-#include "bundle_context.h"
+#include "celix_array_list.h"
 #include "celix_shell.h"
 #include "shell_tui.h"
 #include "utils.h"
@@ -55,13 +47,19 @@
 #define KEY_DEL1               '3'
 #define KEY_DEL2               '~'
 
+const char * const SHELL_NOT_AVAILABLE_MSG = "[Shell TUI] Shell service not 
available.";
+
 struct shell_tui {
     celix_thread_mutex_t mutex; //protects shell
     celix_shell_t* shell;
     celix_thread_t thread;
 
-    int readPipeFd;
-    int writePipeFd;
+    int readStopPipeFd;
+    int writeStopPipeFd;
+
+    int inputFd;
+    FILE* output;
+    FILE* error;
 
     bool useAnsiControlSequences;
 };
@@ -85,25 +83,35 @@ struct OriginalSettings {
 
 // static function declarations
 static void remove_newlines(char* line);
-static void clearLine();
-static void cursorLeft(int n);
-static void writeLine(const char*line, int pos);
-static int autoComplete(celix_shell_t* shellSvc, char *in, int cursorPos, 
size_t maxLen);
+static void clearLine(shell_tui_t*);
+static void cursorLeft(shell_tui_t*, int n);
+static void writeLine(shell_tui_t*, const char*line, int pos);
+static int autoComplete(shell_tui_t*, celix_shell_t* shellSvc, char *in, int 
cursorPos, size_t maxLen);
 static void shellSigHandler(int sig, siginfo_t *info, void* ptr);
 static void* shellTui_runnable(void *data);
-static void shellTui_parseInputForControl(shell_tui_t* shellTui, 
shell_context_t* ctx);
-static void shellTui_parseInput(shell_tui_t* shellTui, shell_context_t* ctx);
-static void writePrompt(void);
+static int shellTui_parseInput(shell_tui_t* shellTui, shell_context_t* ctx);
+static int shellTui_parseInputForControl(shell_tui_t* shellTui, 
shell_context_t* ctx);
+static int shellTui_parseInputPlain(shell_tui_t* shellTui, shell_context_t* 
ctx);
+static void writePrompt(shell_tui_t*);
 
 // Unfortunately has to be static, it is not possible to pass user defined 
data to the handler
 static struct OriginalSettings originalSettings;
 
-shell_tui_t* shellTui_create(bool useAnsiControlSequences) {
+shell_tui_t* shellTui_create(bool useAnsiControlSequences, int inputFd, int 
outputFd, int errorFd) {
     shell_tui_t* result = calloc(1, sizeof(*result));
-    if (result != NULL) {
-        result->useAnsiControlSequences = useAnsiControlSequences;
-        celixThreadMutex_create(&result->mutex, NULL);
+    result->inputFd = inputFd;
+    result->output = outputFd == STDOUT_FILENO ? stdout : fdopen(outputFd, 
"a");
+    if (result->output == NULL) {
+        fprintf(stderr, "Cannot open output fd %i for appending. Falling back 
to stdout\n", outputFd);
+        result->output = stdout;
+    }
+    result->error = errorFd == STDERR_FILENO ? stderr : fdopen(errorFd, "a");
+    if (result->error == NULL) {
+        fprintf(stderr, "Cannot open error fd %i for appending. Falling back 
to stderr\n", errorFd);
+        result->error = stderr;
     }
+    result->useAnsiControlSequences = useAnsiControlSequences;
+    celixThreadMutex_create(&result->mutex, NULL);
     return result;
 }
 
@@ -113,17 +121,16 @@ celix_status_t shellTui_start(shell_tui_t* shellTui) {
     int fds[2];
     int rc  = pipe(fds);
     if (rc == 0) {
-        shellTui->readPipeFd = fds[0];
-        shellTui->writePipeFd = fds[1];
-        if(fcntl(shellTui->writePipeFd, F_SETFL, O_NONBLOCK) == 0){
+        shellTui->readStopPipeFd = fds[0];
+        shellTui->writeStopPipeFd = fds[1];
+        if (fcntl(shellTui->writeStopPipeFd, F_SETFL, O_NONBLOCK) == 0){
                celixThread_create(&shellTui->thread, NULL, shellTui_runnable, 
shellTui);
-        }
-        else{
-               fprintf(stderr,"fcntl on pipe failed");
+        } else {
+               fprintf(shellTui->error,"[Shell TUI] fcntl on pipe failed");
                status = CELIX_FILE_IO_EXCEPTION;
         }
     } else {
-        fprintf(stderr, "Cannot create pipe");
+        fprintf(shellTui->error, "[Shell TUI] Cannot create pipe");
         status = CELIX_BUNDLE_EXCEPTION;
     }
 
@@ -131,17 +138,20 @@ celix_status_t shellTui_start(shell_tui_t* shellTui) {
 }
 
 celix_status_t shellTui_stop(shell_tui_t* shellTui) {
-    celix_status_t status = CELIX_SUCCESS;
-    write(shellTui->writePipeFd, "\0", 1); //trigger select to stop
+    write(shellTui->writeStopPipeFd, "", 1); //trigger select to stop
     celixThread_join(shellTui->thread, NULL);
-    close(shellTui->writePipeFd);
-    close(shellTui->readPipeFd);
-    return status;
+    close(shellTui->writeStopPipeFd);
+    close(shellTui->readStopPipeFd);
+    if (shellTui->output != stdout) {
+        fclose(shellTui->output);
+    }
+    if (shellTui->error != stderr) {
+        fclose(shellTui->error);
+    }
+    return CELIX_SUCCESS;
 }
 
 void shellTui_destroy(shell_tui_t* shellTui) {
-    if (shellTui == NULL) return;
-
     celixThreadMutex_destroy(&shellTui->mutex);
     free(shellTui);
 }
@@ -175,7 +185,7 @@ static void* shellTui_runnable(void *data) {
     ctx.hist = historyCreate();
 
     struct termios term_new;
-    if (shellTui->useAnsiControlSequences) {
+    if (shellTui->useAnsiControlSequences && shellTui->inputFd == 
STDIN_FILENO) {
         sigaction(SIGINT, NULL, &originalSettings.oldSigIntAction);
         sigaction(SIGSEGV, NULL, &originalSettings.oldSigSegvAction);
         sigaction(SIGABRT, NULL, &originalSettings.oldSigAbrtAction);
@@ -196,40 +206,47 @@ static void* shellTui_runnable(void *data) {
         tcsetattr(STDIN_FILENO, TCSANOW, &term_new);
     }
 
-    //setup file descriptors
-    fd_set rfds;
-    int nfds = shellTui->writePipeFd > STDIN_FILENO ? (shellTui->writePipeFd 
+1) : (STDIN_FILENO + 1);
+    //setup poll
+    nfds_t nfds = 2;
+    struct pollfd pollfds[2];
+    pollfds[0].fd = shellTui->readStopPipeFd;
+    pollfds[0].events = POLLIN;
+    pollfds[1].fd = shellTui->inputFd;
+    pollfds[1].events = POLLIN;
 
+    bool printPrompt = true;
     for (;;) {
-        if (shellTui->useAnsiControlSequences) {
-            writeLine(ctx.in, ctx.pos);
-        } else {
-            writePrompt();
+        if (printPrompt && shellTui->useAnsiControlSequences) {
+            writeLine(shellTui, ctx.in, ctx.pos);
+        } else if (printPrompt) {
+            writePrompt(shellTui);
         }
-        FD_ZERO(&rfds);
-        FD_SET(STDIN_FILENO, &rfds);
-        FD_SET(shellTui->readPipeFd, &rfds);
-
-        if (select(nfds, &rfds, NULL, NULL, NULL) > 0) {
-            if (FD_ISSET(shellTui->readPipeFd, &rfds)) {
-                break; //something is written to the pipe -> exit thread
-            } else if (FD_ISSET(STDIN_FILENO, &rfds)) {
-                if (shellTui->useAnsiControlSequences) {
-                    shellTui_parseInputForControl(shellTui, &ctx);
-                } else {
-                    shellTui_parseInput(shellTui, &ctx);
-                }
 
-                if (!isatty(STDIN_FILENO)) {
-                    //not connected to a tty anymore. sleep for 1 sec
-                    usleep(10000000);
-                }
+        int rc = poll(pollfds, nfds, -1);
+        if (rc > 0) {
+            int nrOfCharsRead = 0;
+            if (pollfds[0].revents & POLLIN) {
+                break; //something is written to the stop pipe -> exit thread
             }
+            if (pollfds[1].revents & POLLIN) {
+                //something is written on the STDIN_FILENO fd
+                nrOfCharsRead = shellTui_parseInput(shellTui, &ctx);
+            }
+            printPrompt = nrOfCharsRead > 0;
+            if (shellTui->inputFd == STDIN_FILENO && !isatty(STDIN_FILENO)) {
+                //not connected to a tty (anymore)
+                //sleep for 1 sec to prevent 100% busy loop when a tty is 
removed.
+                usleep(10000000);
+            }
+        } else {
+            //error or (not configured timeout)
+            fprintf(shellTui->error, "[Shell TUI] Error reading stdin: %s\n", 
strerror(errno));
+            break;
         }
     }
 
     historyDestroy(ctx.hist);
-    if (shellTui->useAnsiControlSequences) {
+    if (shellTui->useAnsiControlSequences && shellTui->inputFd == 
STDIN_FILENO) {
         tcsetattr(STDIN_FILENO, TCSANOW, &originalSettings.term_org);
         sigaction(SIGINT, &originalSettings.oldSigIntAction, NULL);
         sigaction(SIGSEGV, &originalSettings.oldSigSegvAction, NULL);
@@ -241,7 +258,15 @@ static void* shellTui_runnable(void *data) {
     return NULL;
 }
 
-static void shellTui_parseInput(shell_tui_t* shellTui, shell_context_t* ctx) {
+static int shellTui_parseInput(shell_tui_t* shellTui, shell_context_t* ctx) {
+    if (shellTui->useAnsiControlSequences) {
+        return shellTui_parseInputForControl(shellTui, ctx);
+    } else {
+        return shellTui_parseInputPlain(shellTui, ctx);
+    }
+}
+
+static int shellTui_parseInputPlain(shell_tui_t* shellTui, shell_context_t* 
ctx) {
     char* buffer = ctx->buffer;
     char* in = ctx->in;
     int pos = ctx->pos;
@@ -249,16 +274,15 @@ static void shellTui_parseInput(shell_tui_t* shellTui, 
shell_context_t* ctx) {
     char* line = NULL;
 
 
-    int nr_chars = read(STDIN_FILENO, buffer, LINE_SIZE-pos-1);
+    int nr_chars = read(shellTui->inputFd, buffer, LINE_SIZE-pos-1);
     for(int bufpos = 0; bufpos < nr_chars; bufpos++) {
         if (buffer[bufpos] == KEY_ENTER) { //end of line -> forward command
             line = utils_stringTrim(in);
             celixThreadMutex_lock(&shellTui->mutex);
             if (shellTui->shell != NULL) {
-                printf("Providing command '%s' from in '%s'\n", line, in);
-                shellTui->shell->executeCommand(shellTui->shell->handle, line, 
stdout, stderr);
+                shellTui->shell->executeCommand(shellTui->shell->handle, line, 
shellTui->output, shellTui->error);
             } else {
-                fprintf(stderr, "Shell service not available\n");
+                fprintf(shellTui->output, "%s\n", SHELL_NOT_AVAILABLE_MSG);
             }
             celixThreadMutex_unlock(&shellTui->mutex);
             pos = 0;
@@ -271,22 +295,18 @@ static void shellTui_parseInput(shell_tui_t* shellTui, 
shell_context_t* ctx) {
         }
     } // for
     ctx->pos = pos;
+    return nr_chars;
 }
 
-static void shellTui_parseInputForControl(shell_tui_t* shellTui, 
shell_context_t* ctx) {
+static int shellTui_parseInputForControl(shell_tui_t* shellTui, 
shell_context_t* ctx) {
     char* buffer = ctx->buffer;
     char* in = ctx->in;
     char* dline = ctx->dline;
     history_t* hist = ctx->hist;
     int pos = ctx->pos;
-
     char* line = NULL;
 
-    if (shellTui == NULL) {
-       return;
-    }
-
-    int nr_chars = read(STDIN_FILENO, buffer, LINE_SIZE-pos-1);
+    int nr_chars = read(shellTui->inputFd, buffer, LINE_SIZE-pos-1);
     for(int bufpos = 0; bufpos < nr_chars; bufpos++) {
         if (buffer[bufpos] == KEY_ESC1 && buffer[bufpos+1] == KEY_ESC2) {
             switch (buffer[bufpos+2]) {
@@ -294,27 +314,27 @@ static void shellTui_parseInputForControl(shell_tui_t* 
shellTui, shell_context_t
                     if(historySize(hist) > 0) {
                         strncpy(in, historyGetPrevLine(hist), LINE_SIZE);
                         pos = strlen(in);
-                        writeLine(in, pos);
+                        writeLine(shellTui, in, pos);
                     }
                     break;
                 case KEY_DOWN:
                     if(historySize(hist) > 0) {
                         strncpy(in, historyGetNextLine(hist), LINE_SIZE);
                         pos = strlen(in);
-                        writeLine(in, pos);
+                        writeLine(shellTui, in, pos);
                     }
                     break;
                 case KEY_RIGHT:
                     if (pos < strlen(in)) {
                         pos++;
                     }
-                    writeLine(in, pos);
+                    writeLine(shellTui, in, pos);
                     break;
                 case KEY_LEFT:
                     if (pos > 0) {
                         pos--;
                     }
-                    writeLine(in, pos);
+                    writeLine(shellTui, in, pos);
                     break;
                 case KEY_DEL1:
                     if(buffer[bufpos+3] == KEY_DEL2) {
@@ -325,7 +345,7 @@ static void shellTui_parseInputForControl(shell_tui_t* 
shellTui, shell_context_t
                                 in[i] = in[i + 1];
                             }
                         }
-                        writeLine(in, pos);
+                        writeLine(shellTui, in, pos);
                     }
                     break;
                 default:
@@ -342,11 +362,11 @@ static void shellTui_parseInputForControl(shell_tui_t* 
shellTui, shell_context_t
                 }
                 pos--;
             }
-            writeLine(in, pos);
+            writeLine(shellTui, in, pos);
             continue;
         } else if(buffer[bufpos] == KEY_TAB) {
             celixThreadMutex_lock(&shellTui->mutex);
-            pos = autoComplete(shellTui->shell, in, pos, LINE_SIZE);
+            pos = autoComplete(shellTui, shellTui->shell, in, pos, LINE_SIZE);
             celixThreadMutex_unlock(&shellTui->mutex);
             continue;
         } else if (buffer[bufpos] != KEY_ENTER) { //not end of line -> text
@@ -355,14 +375,14 @@ static void shellTui_parseInputForControl(shell_tui_t* 
shellTui, shell_context_t
             }
             in[pos] = buffer[bufpos];
             pos++;
-            writeLine(in, pos);
-            fflush(stdout);
+            writeLine(shellTui, in, pos);
+            fflush(shellTui->output);
             continue;
         }
 
         //parse enter
-        writeLine(in, pos);
-        write(STDOUT_FILENO, "\n", 1);
+        writeLine(shellTui, in, pos);
+        fprintf(shellTui->output, "\n");
         remove_newlines(in);
         history_addLine(hist, in);
 
@@ -379,15 +399,15 @@ static void shellTui_parseInputForControl(shell_tui_t* 
shellTui, shell_context_t
         historyLineReset(hist);
         celixThreadMutex_lock(&shellTui->mutex);
         if (shellTui->shell != NULL) {
-            shellTui->shell->executeCommand(shellTui->shell->handle, line, 
stdout, stderr);
-            pos = 0;
-            nr_chars = 0;
+            shellTui->shell->executeCommand(shellTui->shell->handle, line, 
shellTui->output, shellTui->error);
         } else {
-            fprintf(stderr, "Shell service not available\n");
+            fprintf(shellTui->output, "%s\n", SHELL_NOT_AVAILABLE_MSG);
         }
         celixThreadMutex_unlock(&shellTui->mutex);
+        break;
     } // for
     ctx->pos = pos;
+    return nr_chars;
 }
 
 static void remove_newlines(char* line) {
@@ -400,71 +420,102 @@ static void remove_newlines(char* line) {
     }
 }
 
-static void clearLine() {
-       printf("\033[2K\r");
-       fflush(stdout);
+static void clearLine(shell_tui_t* shellTui) {
+       fprintf(shellTui->output, "\033[2K\r");
+       fflush(shellTui->output);
 }
 
-static void cursorLeft(int n) {
-       if(n>0) {
-               printf("\033[%dD", n);
-               fflush(stdout);
+static void cursorLeft(shell_tui_t* shellTui, int n) {
+       if (n>0) {
+               fprintf(shellTui->output, "\033[%dD", n);
        }
+    fflush(shellTui->output);
 }
 
-static void writePrompt(void) {
-    write(STDIN_FILENO, PROMPT, strlen(PROMPT));
+static void writePrompt(shell_tui_t* shellTui) {
+    fwrite(PROMPT, 1, strlen(PROMPT), shellTui->output);
+    fflush(shellTui->output);
 }
 
-static void writeLine(const char* line, int pos) {
-    clearLine();
-    write(STDOUT_FILENO, PROMPT, strlen(PROMPT));
-    write(STDOUT_FILENO, line, strlen(line));
-       cursorLeft(strlen(line)-pos);
+static void writeLine(shell_tui_t* shellTui, const char* line, int pos) {
+    clearLine(shellTui);
+    fwrite( PROMPT, 1, strlen(PROMPT), shellTui->output);
+    fwrite(line, 1, strlen(line), shellTui->output);
+    cursorLeft(shellTui, strlen(line)-pos);
+}
+
+/**
+ * @brief Will check if there is a match with the input and the fully 
qualified cmd name or local name.
+ *
+ * @return Return cmd or local cmd if there is a match with the input.
+ */
+static char* isFullQualifiedOrLocalMatch(char *cmd, char *in, int cursorPos) {
+    char* matchCmd = NULL;
+    if (strncmp(in, cmd, cursorPos) == 0) {
+        matchCmd = cmd;
+    } else {
+        char* namespaceFound = strstr(cmd, "::");
+        if (namespaceFound != NULL) {
+            //got a command with a namespace, strip namespace for a possible 
match. E.g celix::lb -> lb
+            char *localCmd = namespaceFound + 2; //note :: is 2 char, so 
forward 2 chars
+            if (strncmp(in, localCmd, cursorPos) == 0) {
+                matchCmd = localCmd;
+            }
+        }
+    }
+    return matchCmd;
 }
 
-static int autoComplete(celix_shell_t* shellSvc, char *in, int cursorPos, 
size_t maxLen) {
-       array_list_pt commandList = NULL;
-       array_list_pt possibleCmdList = NULL;
+static int autoComplete(shell_tui_t* shellTui, celix_shell_t* shellSvc, char 
*in, int cursorPos, size_t maxLen) {
+       celix_array_list_t* commandList = NULL;
+    celix_array_list_t* possibleCmdList = NULL;
        shellSvc->getCommands(shellSvc->handle, &commandList);
-       int nrCmds = arrayList_size(commandList);
-       arrayList_create(&possibleCmdList);
+       int nrCmds = celix_arrayList_size(commandList);
+    possibleCmdList = celix_arrayList_create();
 
        for (int i = 0; i < nrCmds; i++) {
-               char *cmd = arrayList_get(commandList, i);
-               if (strncmp(in, cmd, cursorPos) == 0) {
-                       arrayList_add(possibleCmdList, cmd);
-               }
+               char *cmd = celix_arrayList_get(commandList, i);
+        char *match = isFullQualifiedOrLocalMatch(cmd, in, cursorPos);
+        if (match != NULL) {
+            celix_arrayList_add(possibleCmdList, match);
+        }
        }
 
-       int nrPossibleCmds = arrayList_size(possibleCmdList);
+       int nrPossibleCmds = celix_arrayList_size(possibleCmdList);
        if (nrPossibleCmds == 0) {
                // Check if complete command with space is entered: show usage 
if this is the case
                if(in[strlen(in) - 1] == ' ') {
                        for (int i = 0; i < nrCmds; i++) {
-                               char *cmd = arrayList_get(commandList, i);
-                               if (strncmp(in, cmd, strlen(cmd)) == 0) {
-                                       clearLine();
-                                       char* usage = NULL;
-                                       
shellSvc->getCommandUsage(shellSvc->handle, cmd, &usage);
-                                       printf("Usage:\n %s\n", usage);
-                               }
+                               char *cmd = celix_arrayList_get(commandList, i);
+                char *match = isFullQualifiedOrLocalMatch(cmd, in, strlen(in) 
- 1);
+                if (match != NULL) {
+                    clearLine(shellTui);
+                    char* usage = NULL;
+                    shellSvc->getCommandUsage(shellSvc->handle, cmd, &usage);
+                    fprintf(shellTui->output, "Usage:\n %s\n", usage);
+                    free(usage);
+                }
                        }
                }
        } else if (nrPossibleCmds == 1) {
                //Replace input string with the only possibility
-               snprintf(in, maxLen, "%s ", 
(char*)arrayList_get(possibleCmdList, 0));
+               snprintf(in, maxLen, "%s ", 
(char*)celix_arrayList_get(possibleCmdList, 0));
                cursorPos = strlen(in);
        } else {
                // Show possibilities
-               clearLine();
+               clearLine(shellTui);
                for(int i = 0; i < nrPossibleCmds; i++) {
-                       printf("%s ", (char*)arrayList_get(possibleCmdList, i));
+                       fprintf(shellTui->output,"%s ", 
(char*)celix_arrayList_get(possibleCmdList, i));
                }
-               printf("\n");
+        fprintf(shellTui->output,"\n");
        }
-       arrayList_destroy(commandList);
-       arrayList_destroy(possibleCmdList);
+
+    for (int i = 0; i < celix_arrayList_size(commandList); ++i) {
+        char* cmd = celix_arrayList_get(commandList, i);
+        free(cmd);
+    }
+    celix_arrayList_destroy(commandList);
+    celix_arrayList_destroy(possibleCmdList);
        return cursorPos;
 }
 
diff --git a/bundles/shell/shell_tui/private/include/shell_tui.h 
b/bundles/shell/shell_tui/src/shell_tui.h
similarity index 66%
rename from bundles/shell/shell_tui/private/include/shell_tui.h
rename to bundles/shell/shell_tui/src/shell_tui.h
index 90061ad..f15e883 100644
--- a/bundles/shell/shell_tui/private/include/shell_tui.h
+++ b/bundles/shell/shell_tui/src/shell_tui.h
@@ -16,13 +16,6 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/**
- * shell_tui.h
- *
- *  \date       Jan 16, 2016
- *  \author            <a href="mailto:[email protected]";>Apache Celix 
Project Team</a>
- *  \copyright Apache License, Version 2.0
- */
 
 #ifndef SHELL_TUI_H_
 #define SHELL_TUI_H_
@@ -34,11 +27,34 @@
 
 typedef struct shell_tui shell_tui_t ;
 
-shell_tui_t* shellTui_create(bool useAnsiControlSequences);
+
+/**
+ * @brief Create a new shell tui.
+ * @param useAnsiControlSequences Whether to parse ansi control sequences.
+ * @param inputFd The input file descriptor to use.
+ * @param outputFd The output file descriptor to use.
+ * @param errorFd The error output file descriptor to use.
+ */
+shell_tui_t* shellTui_create(bool useAnsiControlSequences, int inputFd, int 
outputFd, int errorFd);
+
+/**
+ * @brief Start the shell tui and the thread reading the tty and optional 
extra read file descriptor.
+ */
 celix_status_t shellTui_start(shell_tui_t* shellTui);
+
+/**
+ * @brief Stop the shell tui.
+ */
 celix_status_t shellTui_stop(shell_tui_t* shellTui);
+
+/**
+ * @brief Free the resources for the shell tui
+ */
 void shellTui_destroy(shell_tui_t* shellTui);
 
+/**
+ * @brief set the shell service.
+ */
 celix_status_t shellTui_setShell(shell_tui_t* shellTui, celix_shell_t* svc);
 
 #endif /* SHELL_TUI_H_ */
diff --git a/libs/framework/gtest/src/single_framework_test.cpp 
b/libs/framework/gtest/src/single_framework_test.cpp
index 403707d..8765753 100644
--- a/libs/framework/gtest/src/single_framework_test.cpp
+++ b/libs/framework/gtest/src/single_framework_test.cpp
@@ -19,6 +19,8 @@
 
 #include <gtest/gtest.h>
 #include <atomic>
+#include <chrono>
+#include <thread>
 
 #include "celix_launcher.h"
 #include "celix_framework_factory.h"
@@ -74,6 +76,25 @@ TEST_F(CelixFramework, testEventQueue) {
     EXPECT_EQ(4, count);
 }
 
+TEST_F(CelixFramework, testAsyncInstallStartStopAndUninstallBundle) {
+    long bndId = celix_framework_installBundleAsync(framework.get(), 
SIMPLE_TEST_BUNDLE1_LOCATION, false);
+    EXPECT_GE(bndId, 0);
+    EXPECT_TRUE(celix_framework_isBundleInstalled(framework.get(), bndId));
+    EXPECT_FALSE(celix_framework_isBundleActive(framework.get(), bndId));
+
+    celix_framework_startBundle(framework.get(), bndId);
+    std::this_thread::sleep_for(std::chrono::milliseconds{100});
+    EXPECT_TRUE(celix_framework_isBundleActive(framework.get(), bndId));
+
+    celix_framework_stopBundle(framework.get(), bndId);
+    std::this_thread::sleep_for(std::chrono::milliseconds{100});
+    EXPECT_FALSE(celix_framework_isBundleActive(framework.get(), bndId));
+
+    celix_framework_uninstallBundleAsync(framework.get(), bndId);
+    std::this_thread::sleep_for(std::chrono::milliseconds{100});
+    EXPECT_FALSE(celix_framework_isBundleInstalled(framework.get(), bndId));
+}
+
 class FrameworkFactory : public ::testing::Test {
 public:
     FrameworkFactory() = default;
diff --git a/libs/framework/include/celix_framework.h 
b/libs/framework/include/celix_framework.h
index d50e5b4..5758795 100644
--- a/libs/framework/include/celix_framework.h
+++ b/libs/framework/include/celix_framework.h
@@ -30,13 +30,13 @@ extern "C" {
 #endif
 
 /**
- * Returns the framework UUID. This is unique for every created framework and 
will not be the same if the process is
+ * @brief Returns the framework UUID. This is unique for every created 
framework and will not be the same if the process is
  * restarted.
  */
 const char* celix_framework_getUUID(const celix_framework_t *fw);
 
 /**
- * Returns the framework bundle context. This is the same as a 'normal' bundle 
context and can be used to register, use
+ * @brief Returns the framework bundle context. This is the same as a 'normal' 
bundle context and can be used to register, use
  * and track services. The only difference is that the framework is the bundle.
  * @param fw The framework
  * @return A pointer to the bundle context of the framework or NULL if 
something went wrong.
@@ -44,7 +44,7 @@ const char* celix_framework_getUUID(const celix_framework_t 
*fw);
 celix_bundle_context_t* celix_framework_getFrameworkContext(const 
celix_framework_t *fw);
 
 /**
- * Returns the framework bundle. This is the same as a 'normal' bundle, expect 
that this bundle cannot be uninstalled
+ * @brief Returns the framework bundle. This is the same as a 'normal' bundle, 
expect that this bundle cannot be uninstalled
  * and the `celix_bundle_getEntry` return a entries relative from the working 
directory.
   * @param fw The framework
  * @return A pointer to the bundle of the framework or NULL if something went 
wrong.
@@ -52,7 +52,7 @@ celix_bundle_context_t* 
celix_framework_getFrameworkContext(const celix_framewor
 celix_bundle_t* celix_framework_getFrameworkBundle(const celix_framework_t 
*fw);
 
 /**
- * Use the currently active (started) bundles.
+ * @brief Use the currently active (started) bundles.
  * The provided callback will be called for all the currently started bundles.
  *
  * @param ctx                       The bundle context.
@@ -64,7 +64,7 @@ celix_bundle_t* celix_framework_getFrameworkBundle(const 
celix_framework_t *fw);
 void celix_framework_useBundles(celix_framework_t *fw, bool 
includeFrameworkBundle, void *callbackHandle, void(*use)(void *handle, const 
celix_bundle_t *bnd));
 
 /**
- * Use the bundle with the provided bundle id
+ * @brief Use the bundle with the provided bundle id
  * The provided callback will be called if the bundle is found.
  *
  * @param fw                The framework.
@@ -78,7 +78,7 @@ void celix_framework_useBundles(celix_framework_t *fw, bool 
includeFrameworkBund
 bool celix_framework_useBundle(celix_framework_t *fw, bool onlyActive, long 
bndId, void *callbackHandle, void(*use)(void *handle, const celix_bundle_t 
*bnd));
 
 /**
- * Check whether a bundle is installed.
+ * @brief Check whether a bundle is installed.
  * @param fw        The Celix framework
  * @param bndId     The bundle id to check
  * @return          true if the bundle is installed.
@@ -86,7 +86,7 @@ bool celix_framework_useBundle(celix_framework_t *fw, bool 
onlyActive, long bndI
 bool celix_framework_isBundleInstalled(celix_framework_t *fw, long bndId);
 
 /**
- * Check whether the bundle is active.
+ * @brief Check whether the bundle is active.
  * @param fw        The Celix framework
  * @param bndId     The bundle id to check
  * @return          true if the bundle is installed and active.
@@ -95,7 +95,7 @@ bool celix_framework_isBundleActive(celix_framework_t *fw, 
long bndId);
 
 
 /**
- * Install and optional start a bundle.
+ * @brief Install and optional start a bundle.
  * Will silently ignore bundle ids < 0.
  *
  * @param fw The Celix framework
@@ -106,7 +106,7 @@ bool celix_framework_isBundleActive(celix_framework_t *fw, 
long bndId);
 long celix_framework_installBundle(celix_framework_t *fw, const char 
*bundleLoc, bool autoStart);
 
 /**
- * Uninstall the bundle with the provided bundle id. If needed the bundle will 
be stopped first.
+ * @brief Uninstall the bundle with the provided bundle id. If needed the 
bundle will be stopped first.
  * Will silently ignore bundle ids < 0.
  *
  * @param fw The Celix framework
@@ -116,7 +116,7 @@ long celix_framework_installBundle(celix_framework_t *fw, 
const char *bundleLoc,
 bool celix_framework_uninstallBundle(celix_framework_t *fw, long bndId);
 
 /**
- * Stop the bundle with the provided bundle id.
+ * @brief Stop the bundle with the provided bundle id.
  * Will silently ignore bundle ids < 0.
  *
  * @param fw The Celix framework
@@ -126,7 +126,7 @@ bool celix_framework_uninstallBundle(celix_framework_t *fw, 
long bndId);
 bool celix_framework_stopBundle(celix_framework_t *fw, long bndId);
 
 /**
- * Start the bundle with the provided bundle id.
+ * @brief Start the bundle with the provided bundle id.
  * Will silently ignore bundle ids < 0.
  *
  * @param fw The Celix framework
@@ -136,7 +136,53 @@ bool celix_framework_stopBundle(celix_framework_t *fw, 
long bndId);
 bool celix_framework_startBundle(celix_framework_t *fw, long bndId);
 
 /**
- * Wait until the framework event queue is empty.
+ * @brief Install and optional start a bundle async.
+ * Will silently ignore bundle ids < 0.
+ *
+ * If the bundle needs to be started this will be done a separate spawned 
thread.
+ *
+ * @param fw The Celix framework
+ * @param bundleLoc The bundle location to the bundle zip file.
+ * @param autoStart If the bundle should also be started.
+ * @return The bundle id of the installed bundle or -1 if the bundle could not 
be installed
+ */
+long celix_framework_installBundleAsync(celix_framework_t *fw, const char 
*bundleLoc, bool autoStart);
+
+/**
+ * @brief Uninstall the bundle with the provided bundle id async. If needed 
the bundle will be stopped first.
+ * Will silently ignore bundle ids < 0.
+ *
+ * The bundle will be uninstalled on a separate spawned thread.
+ *
+ * @param fw The Celix framework
+ * @param bndId The bundle id to uninstall.
+ */
+void celix_framework_uninstallBundleAsync(celix_framework_t *fw, long bndId);
+
+/**
+ * @brief Stop the bundle with the provided bundle id async.
+ * Will silently ignore bundle ids < 0.
+ *
+ * The bundle will be stopped on a separate spawned thread.
+ *
+ * @param fw The Celix framework
+ * @param bndId The bundle id to stop.
+ */
+void celix_framework_stopBundleAsync(celix_framework_t *fw, long bndId);
+
+/**
+ * @brief Start the bundle with the provided bundle id async.
+ * Will silently ignore bundle ids < 0.
+ *
+ * The bundle will be started on a separate spawned thread.
+ *
+ * @param fw The Celix framework
+ * @param bndId The bundle id to start.
+ */
+void celix_framework_startBundleAsync(celix_framework_t *fw, long bndId);
+
+/**
+ * @brief Wait until the framework event queue is empty.
  *
  * The Celix framework has an event queue which (among others) handles bundle 
events.
  * This function can be used to ensure that all queue event are handled, 
mainly useful
@@ -147,7 +193,7 @@ bool celix_framework_startBundle(celix_framework_t *fw, 
long bndId);
 void celix_framework_waitForEmptyEventQueue(celix_framework_t *fw);
 
 /**
- * Sets the log function for this framework.
+ * @brief Sets the log function for this framework.
  * Default the celix framework will log to stdout/stderr.
  *
  * A log function can be injected to change how the Celix framework logs.
@@ -157,18 +203,18 @@ void celix_framework_setLogCallback(celix_framework_t* 
fw, void* logHandle, void
 
 
 /**
- * wait until all events for the bundle identified by the bndId are processed.
+ * @brief wait until all events for the bundle identified by the bndId are 
processed.
  */
 void celix_framework_waitUntilNoEventsForBnd(celix_framework_t* fw, long 
bndId);
 
 /**
- * Returns whether the current thread is the Celix framework event loop thread.
+ * @brief Returns whether the current thread is the Celix framework event loop 
thread.
  */
 bool celix_framework_isCurrentThreadTheEventLoop(celix_framework_t* fw);
 
 
 /**
- * Fire a generic event. The event will be added to the event loop and handled 
on the event loop thread.
+ * @brief Fire a generic event. The event will be added to the event loop and 
handled on the event loop thread.
  *
  * if bndId >=0 the bundle usage count will be increased while the event is 
not yet processed or finished processing.
  * The eventName is expected to be const char* valid during til the event is 
finished processing.
@@ -179,7 +225,7 @@ bool 
celix_framework_isCurrentThreadTheEventLoop(celix_framework_t* fw);
 long celix_framework_fireGenericEvent(celix_framework_t* fw, long eventId, 
long bndId, const char *eventName, void* processData, void 
(*processCallback)(void *data), void* doneData, void (*doneCallback)(void* 
doneData));
 
 /**
- * Get the next event id.
+ * @brief Get the next event id.
  *
  * This can be used to ensure celix_framework_waitForGenericEvent can be used 
to wait for an event.
  * The returned event id will not be used by the framework itself unless 
followed up with a
@@ -188,13 +234,13 @@ long celix_framework_fireGenericEvent(celix_framework_t* 
fw, long eventId, long
 long celix_framework_nextEventId(celix_framework_t *fw);
 
 /**
- * Wait until a event with the provided event id is completely handled.
+ * @brief Wait until a event with the provided event id is completely handled.
  * This function will directly return if the provided event id is not in the 
event loop (already done or never issued).
  */
 void celix_framework_waitForGenericEvent(celix_framework_t *fw, long eventId);
 
 /**
- * Wait until the framework is stopped.
+ * @brief Wait until the framework is stopped.
  */
 void celix_framework_waitForStop(celix_framework_t *framework);
 
diff --git a/libs/framework/src/framework.c b/libs/framework/src/framework.c
index c7b289e..e10d259 100644
--- a/libs/framework/src/framework.c
+++ b/libs/framework/src/framework.c
@@ -2168,7 +2168,7 @@ static void 
celix_framework_waitForBundleEvents(celix_framework_t *fw, long bndI
     }
 }
 
-long celix_framework_installBundle(celix_framework_t *fw, const char 
*bundleLoc, bool autoStart) {
+long celix_framework_installBundleInternal(celix_framework_t *fw, const char 
*bundleLoc, bool autoStart, bool forcedAsync) {
     long bundleId = -1;
     bundle_t *bnd = NULL;
     celix_status_t status = CELIX_SUCCESS;
@@ -2179,7 +2179,7 @@ long celix_framework_installBundle(celix_framework_t *fw, 
const char *bundleLoc,
             celix_framework_bundle_entry_t* bndEntry = 
celix_framework_bundleEntry_getBundleEntryAndIncreaseUseCount(fw,
                                                                                
                                      bundleId);
             if (bndEntry != NULL) {
-                status = celix_framework_startBundleOnANonCelixEventThread(fw, 
bndEntry);
+                status = celix_framework_startBundleOnANonCelixEventThread(fw, 
bndEntry, forcedAsync);
                 celix_framework_bundleEntry_decreaseUseCount(bndEntry);
             } else {
                 status = CELIX_ILLEGAL_STATE;
@@ -2193,18 +2193,34 @@ long celix_framework_installBundle(celix_framework_t 
*fw, const char *bundleLoc,
     return bundleId;
 }
 
-bool celix_framework_uninstallBundle(celix_framework_t *fw, long bndId) {
+long celix_framework_installBundle(celix_framework_t *fw, const char 
*bundleLoc, bool autoStart) {
+    return celix_framework_installBundleInternal(fw, bundleLoc, autoStart, 
false);
+}
+
+long celix_framework_installBundleAsync(celix_framework_t *fw, const char 
*bundleLoc, bool autoStart) {
+    return celix_framework_installBundleInternal(fw, bundleLoc, autoStart, 
true);
+}
+
+static bool celix_framework_uninstallBundleInternal(celix_framework_t *fw, 
long bndId, bool forcedAsync) {
     bool uninstalled = false;
     celix_framework_bundle_entry_t *bndEntry = 
celix_framework_bundleEntry_getBundleEntryAndIncreaseUseCount(fw, bndId);
     if (bndEntry != NULL) {
-        celix_status_t status = 
celix_framework_uninstallBundleOnANonCelixEventThread(fw, bndEntry);
+        celix_status_t status = 
celix_framework_uninstallBundleOnANonCelixEventThread(fw, bndEntry, 
forcedAsync);
         celix_framework_waitForBundleEvents(fw, bndId);
-        //note not decreasing bndEntry, because this is not deleted 
(uninstalled)
+        //note not decreasing bndEntry, because this entry should now be 
deleted (uninstalled)
         uninstalled = status == CELIX_SUCCESS;
     }
     return uninstalled;
 }
 
+bool celix_framework_uninstallBundle(celix_framework_t *fw, long bndId) {
+    return celix_framework_uninstallBundleInternal(fw, bndId, false);
+}
+
+void celix_framework_uninstallBundleAsync(celix_framework_t *fw, long bndId) {
+    celix_framework_uninstallBundleInternal(fw, bndId, true);
+}
+
 celix_status_t celix_framework_uninstallBundleEntry(celix_framework_t* 
framework, celix_framework_bundle_entry_t* bndEntry) {
     assert(!celix_framework_isCurrentThreadTheEventLoop(framework));
     celix_bundle_state_e bndState = celix_bundle_getState(bndEntry->bnd);
@@ -2213,6 +2229,7 @@ celix_status_t 
celix_framework_uninstallBundleEntry(celix_framework_t* framework
     }
 
     celix_framework_bundle_entry_t* removedEntry = 
fw_bundleEntry_removeBundleEntryAndIncreaseUseCount(framework, bndEntry->bndId);
+
     celix_framework_bundleEntry_decreaseUseCount(bndEntry);
     if (removedEntry != NULL) {
         celix_status_t status = CELIX_SUCCESS;
@@ -2278,13 +2295,13 @@ celix_status_t 
celix_framework_uninstallBundleEntry(celix_framework_t* framework
     }
 }
 
-bool celix_framework_stopBundle(celix_framework_t *fw, long bndId) {
+static bool celix_framework_stopBundleInternal(celix_framework_t* fw, long 
bndId, bool forcedAsync) {
     bool stopped = false;
     celix_framework_bundle_entry_t *bndEntry = 
celix_framework_bundleEntry_getBundleEntryAndIncreaseUseCount(fw, bndId);
     if (bndEntry != NULL) {
         celix_bundle_state_e state = celix_bundle_getState(bndEntry->bnd);
         if (state == OSGI_FRAMEWORK_BUNDLE_ACTIVE) {
-            celix_status_t rc = 
celix_framework_stopBundleOnANonCelixEventThread(fw, bndEntry);
+            celix_status_t rc = 
celix_framework_stopBundleOnANonCelixEventThread(fw, bndEntry, forcedAsync);
             stopped = rc == CELIX_SUCCESS;
         } else if (state == OSGI_FRAMEWORK_BUNDLE_RESOLVED) {
             //already stopped, silently ignore.
@@ -2297,6 +2314,14 @@ bool celix_framework_stopBundle(celix_framework_t *fw, 
long bndId) {
     return stopped;
 }
 
+bool celix_framework_stopBundle(celix_framework_t *fw, long bndId) {
+    return celix_framework_stopBundleInternal(fw, bndId, false);
+}
+
+void celix_framework_stopBundleAsync(celix_framework_t* fw, long bndId) {
+    celix_framework_stopBundleInternal(fw, bndId, true);
+}
+
 celix_status_t celix_framework_stopBundleEntry(celix_framework_t* framework, 
celix_framework_bundle_entry_t* bndEntry) {
     assert(!celix_framework_isCurrentThreadTheEventLoop(framework));
 
@@ -2408,13 +2433,13 @@ celix_status_t 
celix_framework_stopBundleEntry(celix_framework_t* framework, cel
     return status;
 }
 
-bool celix_framework_startBundle(celix_framework_t *fw, long bndId) {
+bool celix_framework_startBundleInternal(celix_framework_t *fw, long bndId, 
bool forcedAsync) {
     bool started = false;
     celix_framework_bundle_entry_t *bndEntry = 
celix_framework_bundleEntry_getBundleEntryAndIncreaseUseCount(fw, bndId);
     if (bndEntry != NULL) {
         celix_bundle_state_e state = celix_bundle_getState(bndEntry->bnd);
         if (state == OSGI_FRAMEWORK_BUNDLE_INSTALLED || state == 
OSGI_FRAMEWORK_BUNDLE_RESOLVED) {
-            celix_status_t rc = 
celix_framework_startBundleOnANonCelixEventThread(fw, bndEntry);
+            celix_status_t rc = 
celix_framework_startBundleOnANonCelixEventThread(fw, bndEntry, forcedAsync);
             started = rc == CELIX_SUCCESS;
         }
         celix_framework_bundleEntry_decreaseUseCount(bndEntry);
@@ -2423,6 +2448,14 @@ bool celix_framework_startBundle(celix_framework_t *fw, 
long bndId) {
     return started;
 }
 
+bool celix_framework_startBundle(celix_framework_t *fw, long bndId) {
+    return celix_framework_startBundleInternal(fw, bndId, false);
+}
+
+void celix_framework_startBundleAsync(celix_framework_t *fw, long bndId) {
+    celix_framework_startBundleInternal(fw, bndId, true);
+}
+
 celix_status_t celix_framework_startBundleEntry(celix_framework_t* framework, 
celix_framework_bundle_entry_t* bndEntry) {
     assert(!celix_framework_isCurrentThreadTheEventLoop(framework));
     celix_status_t status = CELIX_SUCCESS;
diff --git a/libs/framework/src/framework_bundle_lifecycle_handler.c 
b/libs/framework/src/framework_bundle_lifecycle_handler.c
index c7ef523..09393c3 100644
--- a/libs/framework/src/framework_bundle_lifecycle_handler.c
+++ b/libs/framework/src/framework_bundle_lifecycle_handler.c
@@ -36,12 +36,15 @@ static void* celix_framework_stopStartBundleThread(void 
*data) {
             celix_framework_stopBundleEntry(handler->framework, 
handler->bndEntry);
             break;
         default:
+            celix_framework_bundleEntry_decreaseUseCount(handler->bndEntry);
             celix_framework_uninstallBundleEntry(handler->framework, 
handler->bndEntry);
             break;
     }
     int doneVal = 1;
     __atomic_store(&handler->done, &doneVal, __ATOMIC_SEQ_CST);
-    celix_framework_bundleEntry_decreaseUseCount(handler->bndEntry);
+    if (handler->command != CELIX_BUNDLE_LIFECYCLE_UNINSTALL) {
+        celix_framework_bundleEntry_decreaseUseCount(handler->bndEntry);
+    }
     return NULL;
 }
 
@@ -96,8 +99,12 @@ static void 
celix_framework_createAndStartBundleLifecycleHandler(celix_framework
     celixThreadMutex_unlock(&fw->bundleLifecycleHandling.mutex);
 }
 
-celix_status_t 
celix_framework_startBundleOnANonCelixEventThread(celix_framework_t* fw, 
celix_framework_bundle_entry_t* bndEntry) {
-    if (celix_framework_isCurrentThreadTheEventLoop(fw)) {
+celix_status_t 
celix_framework_startBundleOnANonCelixEventThread(celix_framework_t* fw, 
celix_framework_bundle_entry_t* bndEntry, bool forceSpawnThread) {
+    if (forceSpawnThread) {
+        fw_log(fw->logger, CELIX_LOG_LEVEL_TRACE, "start bundle from a 
separate thread");
+        celix_framework_createAndStartBundleLifecycleHandler(fw, bndEntry, 
CELIX_BUNDLE_LIFECYCLE_START);
+        return CELIX_SUCCESS;
+    } else if (celix_framework_isCurrentThreadTheEventLoop(fw)) {
         fw_log(fw->logger, CELIX_LOG_LEVEL_DEBUG,
                "Cannot start bundle from Celix event thread. Using a separate 
thread to start bundle. See celix_bundleContext_startBundle for more info.");
         celix_framework_createAndStartBundleLifecycleHandler(fw, bndEntry, 
CELIX_BUNDLE_LIFECYCLE_START);
@@ -107,8 +114,12 @@ celix_status_t 
celix_framework_startBundleOnANonCelixEventThread(celix_framework
     }
 }
 
-celix_status_t 
celix_framework_stopBundleOnANonCelixEventThread(celix_framework_t* fw, 
celix_framework_bundle_entry_t* bndEntry) {
-    if (celix_framework_isCurrentThreadTheEventLoop(fw)) {
+celix_status_t 
celix_framework_stopBundleOnANonCelixEventThread(celix_framework_t* fw, 
celix_framework_bundle_entry_t* bndEntry, bool forceSpawnThread) {
+    if (forceSpawnThread) {
+        fw_log(fw->logger, CELIX_LOG_LEVEL_TRACE, "stop bundle from a separate 
thread");
+        celix_framework_createAndStartBundleLifecycleHandler(fw, bndEntry, 
CELIX_BUNDLE_LIFECYCLE_STOP);
+        return CELIX_SUCCESS;
+    } else if (celix_framework_isCurrentThreadTheEventLoop(fw)) {
         fw_log(fw->logger, CELIX_LOG_LEVEL_DEBUG,
                "Cannot stop bundle from Celix event thread. Using a separate 
thread to stop bundle. See celix_bundleContext_startBundle for more info.");
         celix_framework_createAndStartBundleLifecycleHandler(fw, bndEntry, 
CELIX_BUNDLE_LIFECYCLE_STOP);
@@ -118,8 +129,12 @@ celix_status_t 
celix_framework_stopBundleOnANonCelixEventThread(celix_framework_
     }
 }
 
-celix_status_t 
celix_framework_uninstallBundleOnANonCelixEventThread(celix_framework_t* fw, 
celix_framework_bundle_entry_t* bndEntry) {
-    if (celix_framework_isCurrentThreadTheEventLoop(fw)) {
+celix_status_t 
celix_framework_uninstallBundleOnANonCelixEventThread(celix_framework_t* fw, 
celix_framework_bundle_entry_t* bndEntry, bool forceSpawnThread) {
+    if (forceSpawnThread) {
+        fw_log(fw->logger, CELIX_LOG_LEVEL_TRACE, "uninstall bundle from a 
separate thread");
+        celix_framework_createAndStartBundleLifecycleHandler(fw, bndEntry, 
CELIX_BUNDLE_LIFECYCLE_UNINSTALL);
+        return CELIX_SUCCESS;
+    } else if (celix_framework_isCurrentThreadTheEventLoop(fw)) {
         fw_log(fw->logger, CELIX_LOG_LEVEL_DEBUG,
                "Cannot uninstall bundle from Celix event thread. Using a 
separate thread to uninstall bundle. See celix_bundleContext_uninstall Bundle 
for more info.");
         celix_framework_createAndStartBundleLifecycleHandler(fw, bndEntry, 
CELIX_BUNDLE_LIFECYCLE_UNINSTALL);
diff --git a/libs/framework/src/framework_private.h 
b/libs/framework/src/framework_private.h
index 19226b7..6c30ab1 100644
--- a/libs/framework/src/framework_private.h
+++ b/libs/framework/src/framework_private.h
@@ -300,23 +300,37 @@ void 
celix_framework_bundleEntry_decreaseUseCount(celix_framework_bundle_entry_t
  */
 celix_framework_bundle_entry_t* 
celix_framework_bundleEntry_getBundleEntryAndIncreaseUseCount(celix_framework_t 
*fw, long bndId);
 
-/**
- * Start a bundle and ensure that this is not done on the Celix event thread.
- * Will spawn a thread if needed.
- */
-celix_status_t 
celix_framework_startBundleOnANonCelixEventThread(celix_framework_t* fw, 
celix_framework_bundle_entry_t* bndEntry);
+
+
+ /**
+  * Start a bundle and ensure that this is not done on the Celix event thread.
+  * Will spawn a thread if needed.
+  * @param fw The Celix framework
+  * @param bndEntry A bnd entry
+  * @param forceSpawnThread If the true, the start bundle will always be done 
on a spawn thread
+  * @return CELIX_SUCCESS of the call went alright.
+  */
+celix_status_t 
celix_framework_startBundleOnANonCelixEventThread(celix_framework_t* fw, 
celix_framework_bundle_entry_t* bndEntry, bool forceSpawnThread);
 
 /**
  * Stop a bundle and ensure that this is not done on the Celix event thread.
  * Will spawn a thread if needed.
+ * @param fw The Celix framework
+ * @param bndEntry A bnd entry
+ * @param forceSpawnThread If the true, the start bundle will always be done 
on a spawn thread
+ * @return CELIX_SUCCESS of the call went alright.
  */
-celix_status_t 
celix_framework_stopBundleOnANonCelixEventThread(celix_framework_t* fw, 
celix_framework_bundle_entry_t* bndEntry);
+celix_status_t 
celix_framework_stopBundleOnANonCelixEventThread(celix_framework_t* fw, 
celix_framework_bundle_entry_t* bndEntry, bool forceSpawnThread);
 
 /**
  * Uninstall (and if needed stop) a bundle and ensure that this is not done on 
the Celix event thread.
  * Will spawn a thread if needed.
+ * @param fw The Celix framework
+ * @param bndEntry A bnd entry
+ * @param forceSpawnThread If the true, the start bundle will always be done 
on a spawn thread
+ * @return CELIX_SUCCESS of the call went alright.
  */
-celix_status_t 
celix_framework_uninstallBundleOnANonCelixEventThread(celix_framework_t* fw, 
celix_framework_bundle_entry_t* bndEntry);
+celix_status_t 
celix_framework_uninstallBundleOnANonCelixEventThread(celix_framework_t* fw, 
celix_framework_bundle_entry_t* bndEntry, bool forceSpawnThread);
 
 
 /**
@@ -337,7 +351,7 @@ celix_status_t 
celix_framework_startBundleEntry(celix_framework_t* fw, celix_fra
 celix_status_t celix_framework_stopBundleEntry(celix_framework_t* fw, 
celix_framework_bundle_entry_t* bndEntry);
 
 /**
- * Uinstall a bundle. Cannot be called on the Celix event thread.
+ * Uninstall a bundle. Cannot be called on the Celix event thread.
  */
 celix_status_t celix_framework_uninstallBundleEntry(celix_framework_t* fw, 
celix_framework_bundle_entry_t* bndEntry);
 

Reply via email to