Grzegorz Grzybek created KARAF-7126: ---------------------------------------
Summary: State overwrite when installing more features at once Key: KARAF-7126 URL: https://issues.apache.org/jira/browse/KARAF-7126 Project: Karaf Issue Type: Bug Affects Versions: 4.3.1, 4.2.11 Reporter: Grzegorz Grzybek Assignee: Grzegorz Grzybek Fix For: 4.2.12, 4.3.2 I had a case, where (which is not recommended) two feature XML files were dropped into {{deploy/}} directory. _Often_ (but not always) one of the features was not marked as "Started" - and it happened in random order. It can be reproduced with {{simple1.xml}} file: {code:xml} <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <features xmlns="http://karaf.apache.org/xmlns/features/v1.3.0" name="simple1"> <feature name="simple1" version="0.0.1" install="auto"> <bundle start-level="80" start="true">mvn:commons-io/commons-io/2.8.0</bundle> </feature> </features> {code} and {{simple2.xml}}: {code:xml} <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <features xmlns="http://karaf.apache.org/xmlns/features/v1.3.0" name="simple2"> <feature name="simple2" version="0.0.1" install="auto"> <bundle start-level="80" start="true">mvn:commons-collections/commons-collections/3.2.2</bundle> </feature> </features> {code} dropped at the same time to {{deploy/}} directory. Here's my investigation: 1. deploy two files 2. these artifacts are processed in single fileinstall thread: {noformat} 0 = {org.apache.felix.fileinstall.internal.Artifact@11682} path: java.io.File = {java.io.File@11683} "/data/karaf/deploy/simple1.xml" jaredDirectory: java.io.File = {java.io.File@11683} "/data/karaf/deploy/simple1.xml" jaredUrl: java.net.URL = {java.net.URL@11703} "file:/data/karaf/deploy/simple1.xml" listener: org.apache.felix.fileinstall.ArtifactListener = {org.apache.karaf.deployer.features.FeatureDeploymentListener@11698} transformedUrl: java.net.URL = {java.net.URL@11646} "feature:file:/data/karaf/deploy/simple1.xml" transformed: java.io.File = null bundleId: long = -1 (0xFFFFFFFFFFFFFFFF) checksum: long = 1818582559 (0x6C655E1F) 1 = {org.apache.felix.fileinstall.internal.Artifact@11694} path: java.io.File = {java.io.File@11696} "/data/karaf/deploy/simple2.xml" jaredDirectory: java.io.File = {java.io.File@11696} "/data/karaf/deploy/simple2.xml" jaredUrl: java.net.URL = {java.net.URL@11697} "file:/data/karaf/deploy/simple2.xml" listener: org.apache.felix.fileinstall.ArtifactListener = {org.apache.karaf.deployer.features.FeatureDeploymentListener@11698} transformedUrl: java.net.URL = {java.net.URL@11699} "feature:file:/data/karaf/deploy/simple2.xml" transformed: java.io.File = null bundleId: long = -1 (0xFFFFFFFFFFFFFFFF) checksum: long = 3138423735 (0xBB108BB7) 3. single instance of org.apache.karaf.deployer.features.FeatureURLHandler has its org.apache.karaf.deployer.features.FeatureURLHandler#featureXmlURL set to file:/data/karaf/deploy/simple1.xml stack trace: {noformat} "fileinstall-/data/karaf/deploy@11585" daemon prio=5 tid=0x2e nid=NA runnable java.lang.Thread.State: RUNNABLE at org.apache.karaf.deployer.features.FeatureURLHandler.openConnection(FeatureURLHandler.java:56) at org.apache.felix.framework.URLHandlersStreamHandlerProxy.openConnection(URLHandlersStreamHandlerProxy.java:271) at java.net.URL.openConnection(URL.java:1002) at java.net.URL.openStream(URL.java:1068) at org.apache.felix.fileinstall.internal.DirectoryWatcher.install(DirectoryWatcher.java:962) at org.apache.felix.fileinstall.internal.DirectoryWatcher.install(DirectoryWatcher.java:884) at org.apache.felix.fileinstall.internal.DirectoryWatcher.doProcess(DirectoryWatcher.java:489) at org.apache.felix.fileinstall.internal.DirectoryWatcher.process(DirectoryWatcher.java:365) at org.apache.felix.fileinstall.internal.DirectoryWatcher.run(DirectoryWatcher.java:316) {noformat} 4. in org.apache.felix.fileinstall.internal.DirectoryWatcher#installOrUpdateBundle, simple1 is processed 5. log message: {noformat} Installing bundle simple1.xml / 0.0.0 {noformat} 6. org.osgi.framework.BundleContext#installBundle() for simple1 7. featureXmlURL is set to simple2.xml 8. log message: {noformat} Installing bundle simple2.xml / 0.0.0 {noformat} 9. org.osgi.framework.BundleContext#installBundle() for simple2 Now: {noformat} 240 │ Installed │ 80 │ 0.0.0 │ feature:file:/data/karaf/deploy/simple1.xml 241 │ Installed │ 80 │ 0.0.0 │ feature:file:/data/karaf/deploy/simple2.xml {noformat} 10. Of course org.apache.felix.fileinstall.internal.DirectoryWatcher#doProcess() uses HashMaps... that's why bundle2 may be started earlier 11. org.apache.felix.fileinstall.internal.FileInstall#refresh() is called for the two bundles in random order - but they're still in INSTALLED state 12. org.apache.felix.fileinstall.internal.DirectoryWatcher#startBundles() is called 13. org.osgi.framework.Bundle#start(int) is called for each bundle and the event is caught by: {noformat} "FelixDispatchQueue@11772" prio=5 tid=0xd nid=NA runnable java.lang.Thread.State: RUNNABLE at org.apache.karaf.deployer.features.FeatureDeploymentListener.bundleChanged(FeatureDeploymentListener.java:186) - locked <0x2db2> (a org.apache.karaf.deployer.features.FeatureDeploymentListener) at org.apache.felix.framework.EventDispatcher.invokeBundleListenerCallback(EventDispatcher.java:915) at org.apache.felix.framework.EventDispatcher.fireEventImmediately(EventDispatcher.java:834) at org.apache.felix.framework.EventDispatcher.run(EventDispatcher.java:1147) at org.apache.felix.framework.EventDispatcher.access$000(EventDispatcher.java:54) at org.apache.felix.framework.EventDispatcher$1.run(EventDispatcher.java:102) at java.lang.Thread.run(Thread.java:748) {noformat} 14. eventually two tasks are correctly passed to single featuresService pool the first one (running). It has these vars reachable: {noformat} this = {org.apache.karaf.features.internal.service.FeaturesServiceImpl$$Lambda$108.830131376@12778} arg$1: org.apache.karaf.features.internal.service.FeaturesServiceImpl = {org.apache.karaf.features.internal.service.FeaturesServiceImpl@12631} arg$2: java.util.Set = {java.util.HashSet@12648} size = 1 arg$3: java.util.Map = {java.util.HashMap@12643} size = 1 arg$4: java.util.Map = {java.util.Collections$EmptyMap@12644} size = 0 arg$5: org.apache.karaf.features.internal.service.State = {org.apache.karaf.features.internal.service.State@12645} arg$6: java.util.Map = {java.util.HashMap@12646} size = 621 arg$7: java.util.EnumSet = {java.util.RegularEnumSet@12647} size = 0 arg$8: java.lang.String = null {noformat} the second one (waiting). It has these vars reachable: {noformat} callable: java.util.concurrent.Callable = {org.apache.karaf.features.internal.service.FeaturesServiceImpl$$Lambda$108.830131376@12760} arg$1: org.apache.karaf.features.internal.service.FeaturesServiceImpl = {org.apache.karaf.features.internal.service.FeaturesServiceImpl@12631} arg$2: java.util.Set = {java.util.HashSet@12663} size = 1 arg$3: java.util.Map = {java.util.HashMap@12659} size = 1 arg$4: java.util.Map = {java.util.Collections$EmptyMap@12644} size = 0 arg$5: org.apache.karaf.features.internal.service.State = {org.apache.karaf.features.internal.service.State@12660} arg$6: java.util.Map = {java.util.HashMap@12661} size = 622 arg$7: java.util.EnumSet = {java.util.RegularEnumSet@12662} size = 0 arg$8: java.lang.String = null {noformat} The only important difference is that 2nd task already has larger map of features. State of the running task: {noformat} state = {org.apache.karaf.features.internal.service.State@12645} bootDone: java.util.concurrent.atomic.AtomicBoolean = {java.util.concurrent.atomic.AtomicBoolean@17577} "true" repositories: java.util.Set = {java.util.TreeSet@17578} size = 13 requirements: java.util.Map = {java.util.HashMap@17579} size = 1 installedFeatures: java.util.Map = {java.util.HashMap@17580} size = 1 stateFeatures: java.util.Map = {java.util.HashMap@17581} size = 1 managedBundles: java.util.Map = {java.util.HashMap@17582} size = 1 bundleChecksums: java.util.Map = {java.util.HashMap@17583} size = 0 {noformat} State of the waiting task: {noformat} arg$5: org.apache.karaf.features.internal.service.State = {org.apache.karaf.features.internal.service.State@12660} bootDone: java.util.concurrent.atomic.AtomicBoolean = {java.util.concurrent.atomic.AtomicBoolean@17644} "true" repositories: java.util.Set = {java.util.TreeSet@17645} size = 14 requirements: java.util.Map = {java.util.HashMap@17646} size = 1 installedFeatures: java.util.Map = {java.util.HashMap@17647} size = 1 stateFeatures: java.util.Map = {java.util.HashMap@17648} size = 1 managedBundles: java.util.Map = {java.util.HashMap@17649} size = 1 bundleChecksums: java.util.Map = {java.util.HashMap@17650} size = 0 {noformat} 15. Even at first line of org.apache.karaf.features.internal.service.FeaturesServiceImpl#doProvision(), feature:list already gives me: {noformat} karaf@root()> feature:list | grep simple simple1 │ 0.0.1 │ │ Uninstalled │ simple1 │ simple2 │ 0.0.1 │ │ Uninstalled │ simple2 │ {noformat} 16. When 2nd task runs, there are two State instances: the state attached to 2nd task just started: {noformat} State@12660 bootDone: java.util.concurrent.atomic.AtomicBoolean = {java.util.concurrent.atomic.AtomicBoolean@17644} "true" repositories: java.util.Set = {java.util.TreeSet@17645} size = 14 requirements: java.util.Map = {java.util.HashMap@17646} size = 1 installedFeatures: java.util.Map = {java.util.HashMap@17647} size = 1 "root" -> {java.util.HashSet@52251} size = 120 stateFeatures: java.util.Map = {java.util.HashMap@17648} size = 1 managedBundles: java.util.Map = {java.util.HashMap@17649} size = 1 bundleChecksums: java.util.Map = {java.util.HashMap@17650} size = 0 {noformat} the state copied to features service after first task finishes - in this state, simple1 is already installed {noformat} State@12846 bootDone: java.util.concurrent.atomic.AtomicBoolean = {java.util.concurrent.atomic.AtomicBoolean@52374} "true" repositories: java.util.Set = {java.util.TreeSet@17833} size = 14 requirements: java.util.Map = {java.util.HashMap@52375} size = 1 installedFeatures: java.util.Map = {java.util.HashMap@52376} size = 1 "root" -> {java.util.HashSet@52384} size = 121 stateFeatures: java.util.Map = {java.util.HashMap@52377} size = 1 managedBundles: java.util.Map = {java.util.HashMap@52378} size = 1 bundleChecksums: java.util.Map = {java.util.HashMap@52379} size = 0 {noformat} 17. so far, so good: {noformat} karaf@root()> feature:list | grep simple simple1 │ 0.0.1 │ │ Uninstalled │ simple1 │ simple2 │ 0.0.1 │ x │ Started │ simple2 │ {noformat} 18. (different debugger run, so addresses have changed) here's how state after first feature installation is used to replace existing state: existing: {noformat} this.state = {org.apache.karaf.features.internal.service.State@39249} bootDone: java.util.concurrent.atomic.AtomicBoolean = {java.util.concurrent.atomic.AtomicBoolean@39250} "true" repositories: java.util.Set = {java.util.TreeSet@39257} size = 14 requirements: java.util.Map = {java.util.HashMap@39258} size = 1 installedFeatures: java.util.Map = {java.util.HashMap@39259} size = 1 "root" -> {java.util.HashSet@39266} size = 120 stateFeatures: java.util.Map = {java.util.HashMap@39260} size = 1 managedBundles: java.util.Map = {java.util.HashMap@39261} size = 1 bundleChecksums: java.util.Map = {java.util.HashMap@39262} size = 0 {noformat} new: {noformat} state = {org.apache.karaf.features.internal.service.State@39248} bootDone: java.util.concurrent.atomic.AtomicBoolean = {java.util.concurrent.atomic.AtomicBoolean@39251} "true" repositories: java.util.Set = {java.util.TreeSet@39267} size = 14 requirements: java.util.Map = {java.util.HashMap@39268} size = 1 installedFeatures: java.util.Map = {java.util.HashMap@39269} size = 1 "root" -> {java.util.HashSet@39276} size = 121 stateFeatures: java.util.Map = {java.util.HashMap@39270} size = 1 managedBundles: java.util.Map = {java.util.HashMap@39271} size = 1 bundleChecksums: java.util.Map = {java.util.HashMap@39272} size = 0 {noformat} 19. and second feature installation: existing (simple1 is installed in `installedFeatures`): {noformat} this.state = {org.apache.karaf.features.internal.service.State@39249} bootDone: java.util.concurrent.atomic.AtomicBoolean = {java.util.concurrent.atomic.AtomicBoolean@39250} "true" repositories: java.util.Set = {java.util.TreeSet@39257} size = 14 requirements: java.util.Map = {java.util.HashMap@39258} size = 1 installedFeatures: java.util.Map = {java.util.HashMap@39259} size = 1 "root" -> {java.util.HashSet@44230} size = 121 stateFeatures: java.util.Map = {java.util.HashMap@39260} size = 1 managedBundles: java.util.Map = {java.util.HashMap@39261} size = 1 bundleChecksums: java.util.Map = {java.util.HashMap@39262} size = 0 {noformat} new (simple2 is installed in `installedFeatures`): {noformat} state = {org.apache.karaf.features.internal.service.State@44226} bootDone: java.util.concurrent.atomic.AtomicBoolean = {java.util.concurrent.atomic.AtomicBoolean@44227} "true" repositories: java.util.Set = {java.util.TreeSet@44231} size = 14 requirements: java.util.Map = {java.util.HashMap@44232} size = 1 installedFeatures: java.util.Map = {java.util.HashMap@44233} size = 1 "root" -> {java.util.HashSet@44240} size = 121 stateFeatures: java.util.Map = {java.util.HashMap@44234} size = 1 managedBundles: java.util.Map = {java.util.HashMap@44235} size = 1 bundleChecksums: java.util.Map = {java.util.HashMap@44236} size = 0 {noformat} And here's the problem - scheduled provisioning tasks capture some State and the 2nd task which runs doesn't update its state after first task finishes. This is reproducible also with simple: {noformat} karaf@root()> feature:install simple1 & feature:install simple2 [1] background feature:install simple1 [1] done feature:install simple1 karaf@root()> karaf@root()> feature:list|grep simple simple2 │ 0.0.1 │ x │ Started │ simple2 │ simple1 │ 0.0.1 │ │ Uninstalled │ simple1 │ {noformat} -- This message was sent by Atlassian Jira (v8.3.4#803005)