@voxgig/sdkgen 4.34.1 → 4.34.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/voxgig-sdkgen +1 -1
- package/dist/cmp/ReadmeErrors.js +3 -3
- package/dist/cmp/ReadmeErrors.js.map +1 -1
- package/dist/cmp/ReadmeExplanation.js +3 -3
- package/dist/cmp/ReadmeExplanation.js.map +1 -1
- package/dist/cmp/ReadmeQuick.js +3 -1
- package/dist/cmp/ReadmeQuick.js.map +1 -1
- package/dist/cmp/ReadmeTop.js +9 -8
- package/dist/cmp/ReadmeTop.js.map +1 -1
- package/dist/helpers/definitionPlan.js +8 -4
- package/dist/helpers/definitionPlan.js.map +1 -1
- package/dist/helpers/naming.d.ts +2 -1
- package/dist/helpers/naming.js +11 -4
- package/dist/helpers/naming.js.map +1 -1
- package/dist/helpers/opExample.d.ts +16 -2
- package/dist/helpers/opExample.js +80 -21
- package/dist/helpers/opExample.js.map +1 -1
- package/dist/helpers/opShape.d.ts +3 -1
- package/dist/helpers/opShape.js +21 -8
- package/dist/helpers/opShape.js.map +1 -1
- package/dist/sdkgen.d.ts +4 -4
- package/dist/sdkgen.js +12 -4
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utility.d.ts +2 -1
- package/dist/utility.js +29 -3
- package/dist/utility.js.map +1 -1
- package/package.json +2 -2
- package/project/.sdk/src/cmp/c/ReadmeEntity_c.ts +3 -2
- package/project/.sdk/src/cmp/c/ReadmeHowto_c.ts +7 -3
- package/project/.sdk/src/cmp/c/ReadmeQuick_c.ts +3 -2
- package/project/.sdk/src/cmp/c/ReadmeRef_c.ts +5 -4
- package/project/.sdk/src/cmp/c/ReadmeTopQuick_c.ts +3 -2
- package/project/.sdk/src/cmp/c/ReadmeTopTest_c.ts +8 -7
- package/project/.sdk/src/cmp/c/utility_c.ts +21 -2
- package/project/.sdk/src/cmp/clojure/ReadmeEntity_clojure.ts +4 -1
- package/project/.sdk/src/cmp/clojure/ReadmeHowto_clojure.ts +17 -6
- package/project/.sdk/src/cmp/clojure/ReadmeModel_clojure.ts +6 -6
- package/project/.sdk/src/cmp/clojure/ReadmeQuick_clojure.ts +13 -8
- package/project/.sdk/src/cmp/clojure/ReadmeRef_clojure.ts +21 -18
- package/project/.sdk/src/cmp/clojure/ReadmeTopQuick_clojure.ts +6 -3
- package/project/.sdk/src/cmp/clojure/ReadmeTopTest_clojure.ts +16 -9
- package/project/.sdk/src/cmp/clojure/TestClean_clojure.ts +4 -4
- package/project/.sdk/src/cmp/clojure/fragment/Entity.fragment.clj +2 -1
- package/project/.sdk/src/cmp/clojure/utility_clojure.ts +14 -0
- package/project/.sdk/src/cmp/cpp/ReadmeEntity_cpp.ts +3 -2
- package/project/.sdk/src/cmp/cpp/ReadmeHowto_cpp.ts +7 -3
- package/project/.sdk/src/cmp/cpp/ReadmeQuick_cpp.ts +3 -2
- package/project/.sdk/src/cmp/cpp/ReadmeRef_cpp.ts +6 -5
- package/project/.sdk/src/cmp/cpp/ReadmeTopQuick_cpp.ts +3 -2
- package/project/.sdk/src/cmp/cpp/ReadmeTopTest_cpp.ts +8 -7
- package/project/.sdk/src/cmp/cpp/utility_cpp.ts +13 -0
- package/project/.sdk/src/cmp/csharp/ReadmeEntity_csharp.ts +3 -2
- package/project/.sdk/src/cmp/csharp/ReadmeHowto_csharp.ts +20 -8
- package/project/.sdk/src/cmp/csharp/ReadmeQuick_csharp.ts +13 -8
- package/project/.sdk/src/cmp/csharp/ReadmeRef_csharp.ts +10 -5
- package/project/.sdk/src/cmp/csharp/ReadmeTopQuick_csharp.ts +14 -7
- package/project/.sdk/src/cmp/csharp/ReadmeTopTest_csharp.ts +18 -10
- package/project/.sdk/src/cmp/csharp/utility_csharp.ts +13 -0
- package/project/.sdk/src/cmp/elixir/ReadmeEntity_elixir.ts +4 -4
- package/project/.sdk/src/cmp/elixir/ReadmeExplanation_elixir.ts +2 -1
- package/project/.sdk/src/cmp/elixir/ReadmeHowto_elixir.ts +13 -6
- package/project/.sdk/src/cmp/elixir/ReadmeModel_elixir.ts +5 -5
- package/project/.sdk/src/cmp/elixir/ReadmeQuick_elixir.ts +13 -10
- package/project/.sdk/src/cmp/elixir/ReadmeRef_elixir.ts +11 -11
- package/project/.sdk/src/cmp/elixir/ReadmeTopQuick_elixir.ts +8 -6
- package/project/.sdk/src/cmp/elixir/ReadmeTopTest_elixir.ts +14 -10
- package/project/.sdk/src/cmp/elixir/fragment/EntityCreateOp.fragment.ex +4 -3
- package/project/.sdk/src/cmp/elixir/fragment/EntityListOp.fragment.ex +4 -3
- package/project/.sdk/src/cmp/elixir/fragment/EntityLoadOp.fragment.ex +4 -3
- package/project/.sdk/src/cmp/elixir/fragment/EntityPatchOp.fragment.ex +4 -3
- package/project/.sdk/src/cmp/elixir/fragment/EntityRemoveOp.fragment.ex +4 -3
- package/project/.sdk/src/cmp/elixir/fragment/EntityUpdateOp.fragment.ex +4 -3
- package/project/.sdk/src/cmp/elixir/utility_elixir.ts +12 -0
- package/project/.sdk/src/cmp/go/ReadmeEntity_go.ts +8 -5
- package/project/.sdk/src/cmp/go/ReadmeExplanation_go.ts +3 -1
- package/project/.sdk/src/cmp/go/ReadmeHowto_go.ts +20 -8
- package/project/.sdk/src/cmp/go/ReadmeModel_go.ts +16 -13
- package/project/.sdk/src/cmp/go/ReadmeQuick_go.ts +14 -13
- package/project/.sdk/src/cmp/go/ReadmeRef_go.ts +14 -13
- package/project/.sdk/src/cmp/go/ReadmeTopQuick_go.ts +10 -8
- package/project/.sdk/src/cmp/go/ReadmeTopTest_go.ts +6 -2
- package/project/.sdk/src/cmp/go/utility_go.ts +1 -1
- package/project/.sdk/src/cmp/java/ReadmeEntity_java.ts +8 -19
- package/project/.sdk/src/cmp/java/ReadmeHowto_java.ts +19 -22
- package/project/.sdk/src/cmp/java/ReadmeModel_java.ts +10 -9
- package/project/.sdk/src/cmp/java/ReadmeQuick_java.ts +24 -28
- package/project/.sdk/src/cmp/java/ReadmeRef_java.ts +27 -35
- package/project/.sdk/src/cmp/java/ReadmeTopQuick_java.ts +22 -25
- package/project/.sdk/src/cmp/java/ReadmeTopTest_java.ts +18 -24
- package/project/.sdk/src/cmp/java/utility_java.ts +25 -0
- package/project/.sdk/src/cmp/js/EntityOperation_js.ts +3 -1
- package/project/.sdk/src/cmp/js/ReadmeEntity_js.ts +2 -8
- package/project/.sdk/src/cmp/js/ReadmeHowto_js.ts +11 -9
- package/project/.sdk/src/cmp/js/ReadmeModel_js.ts +9 -9
- package/project/.sdk/src/cmp/js/ReadmeQuick_js.ts +7 -13
- package/project/.sdk/src/cmp/js/ReadmeRef_js.ts +14 -15
- package/project/.sdk/src/cmp/js/ReadmeTopQuick_js.ts +16 -11
- package/project/.sdk/src/cmp/js/ReadmeTopTest_js.ts +11 -7
- package/project/.sdk/src/cmp/js/fragment/EntityCreateOp.fragment.js +1 -1
- package/project/.sdk/src/cmp/js/fragment/EntityListOp.fragment.js +1 -1
- package/project/.sdk/src/cmp/js/fragment/EntityLoadOp.fragment.js +1 -1
- package/project/.sdk/src/cmp/js/fragment/EntityPatchOp.fragment.js +1 -1
- package/project/.sdk/src/cmp/js/fragment/EntityRemoveOp.fragment.js +1 -1
- package/project/.sdk/src/cmp/js/fragment/EntityUpdateOp.fragment.js +1 -1
- package/project/.sdk/src/cmp/js/utility_js.ts +2 -1
- package/project/.sdk/src/cmp/kotlin/ReadmeEntity_kotlin.ts +3 -2
- package/project/.sdk/src/cmp/kotlin/ReadmeHowto_kotlin.ts +19 -9
- package/project/.sdk/src/cmp/kotlin/ReadmeModel_kotlin.ts +10 -9
- package/project/.sdk/src/cmp/kotlin/ReadmeQuick_kotlin.ts +14 -9
- package/project/.sdk/src/cmp/kotlin/ReadmeRef_kotlin.ts +18 -15
- package/project/.sdk/src/cmp/kotlin/ReadmeTopQuick_kotlin.ts +14 -8
- package/project/.sdk/src/cmp/kotlin/ReadmeTopTest_kotlin.ts +17 -10
- package/project/.sdk/src/cmp/kotlin/utility_kotlin.ts +21 -1
- package/project/.sdk/src/cmp/lua/ReadmeEntity_lua.ts +3 -2
- package/project/.sdk/src/cmp/lua/ReadmeExamplesTest_lua.ts +7 -4
- package/project/.sdk/src/cmp/lua/ReadmeHowto_lua.ts +9 -7
- package/project/.sdk/src/cmp/lua/ReadmeModel_lua.ts +13 -13
- package/project/.sdk/src/cmp/lua/ReadmeQuick_lua.ts +20 -9
- package/project/.sdk/src/cmp/lua/ReadmeRef_lua.ts +9 -8
- package/project/.sdk/src/cmp/lua/ReadmeTopQuick_lua.ts +15 -7
- package/project/.sdk/src/cmp/lua/ReadmeTopTest_lua.ts +6 -5
- package/project/.sdk/src/cmp/lua/fragment/Entity.fragment.lua +1 -0
- package/project/.sdk/src/cmp/lua/fragment/EntityCreateOp.fragment.lua +1 -1
- package/project/.sdk/src/cmp/lua/fragment/EntityListOp.fragment.lua +1 -1
- package/project/.sdk/src/cmp/lua/fragment/EntityLoadOp.fragment.lua +1 -1
- package/project/.sdk/src/cmp/lua/fragment/EntityPatchOp.fragment.lua +1 -1
- package/project/.sdk/src/cmp/lua/fragment/EntityRemoveOp.fragment.lua +1 -1
- package/project/.sdk/src/cmp/lua/fragment/EntityUpdateOp.fragment.lua +1 -1
- package/project/.sdk/src/cmp/ocaml/ReadmeEntity_ocaml.ts +3 -2
- package/project/.sdk/src/cmp/ocaml/ReadmeHowto_ocaml.ts +3 -2
- package/project/.sdk/src/cmp/ocaml/ReadmeModel_ocaml.ts +1 -0
- package/project/.sdk/src/cmp/ocaml/ReadmeQuick_ocaml.ts +5 -3
- package/project/.sdk/src/cmp/ocaml/ReadmeRef_ocaml.ts +10 -8
- package/project/.sdk/src/cmp/ocaml/ReadmeTopQuick_ocaml.ts +4 -2
- package/project/.sdk/src/cmp/ocaml/ReadmeTopTest_ocaml.ts +3 -2
- package/project/.sdk/src/cmp/ocaml/utility_ocaml.ts +13 -1
- package/project/.sdk/src/cmp/perl/ReadmeEntity_perl.ts +4 -1
- package/project/.sdk/src/cmp/perl/ReadmeHowto_perl.ts +10 -5
- package/project/.sdk/src/cmp/perl/ReadmeModel_perl.ts +6 -6
- package/project/.sdk/src/cmp/perl/ReadmeQuick_perl.ts +8 -5
- package/project/.sdk/src/cmp/perl/ReadmeRef_perl.ts +21 -18
- package/project/.sdk/src/cmp/perl/ReadmeTopQuick_perl.ts +6 -3
- package/project/.sdk/src/cmp/perl/ReadmeTopTest_perl.ts +11 -8
- package/project/.sdk/src/cmp/perl/fragment/Entity.fragment.pm +1 -1
- package/project/.sdk/src/cmp/perl/fragment/EntityCreateOp.fragment.pm +2 -2
- package/project/.sdk/src/cmp/perl/fragment/EntityLoadOp.fragment.pm +2 -2
- package/project/.sdk/src/cmp/perl/fragment/EntityPatchOp.fragment.pm +2 -2
- package/project/.sdk/src/cmp/perl/fragment/EntityRemoveOp.fragment.pm +1 -1
- package/project/.sdk/src/cmp/perl/fragment/EntityUpdateOp.fragment.pm +2 -2
- package/project/.sdk/src/cmp/perl/utility_perl.ts +11 -1
- package/project/.sdk/src/cmp/php/EntityOperation_php.ts +3 -1
- package/project/.sdk/src/cmp/php/ReadmeEntity_php.ts +4 -3
- package/project/.sdk/src/cmp/php/ReadmeHowto_php.ts +12 -6
- package/project/.sdk/src/cmp/php/ReadmeModel_php.ts +9 -9
- package/project/.sdk/src/cmp/php/ReadmeQuick_php.ts +3 -2
- package/project/.sdk/src/cmp/php/ReadmeRef_php.ts +15 -14
- package/project/.sdk/src/cmp/php/ReadmeTopQuick_php.ts +5 -4
- package/project/.sdk/src/cmp/php/ReadmeTopTest_php.ts +13 -7
- package/project/.sdk/src/cmp/php/fragment/EntityCreateOp.fragment.php +2 -2
- package/project/.sdk/src/cmp/php/fragment/EntityListOp.fragment.php +2 -2
- package/project/.sdk/src/cmp/php/fragment/EntityLoadOp.fragment.php +2 -2
- package/project/.sdk/src/cmp/php/fragment/EntityPatchOp.fragment.php +2 -2
- package/project/.sdk/src/cmp/php/fragment/EntityRemoveOp.fragment.php +2 -2
- package/project/.sdk/src/cmp/php/fragment/EntityUpdateOp.fragment.php +2 -2
- package/project/.sdk/src/cmp/py/EntityOperation_py.ts +3 -1
- package/project/.sdk/src/cmp/py/ReadmeEntity_py.ts +3 -8
- package/project/.sdk/src/cmp/py/ReadmeHowto_py.ts +9 -9
- package/project/.sdk/src/cmp/py/ReadmeModel_py.ts +9 -9
- package/project/.sdk/src/cmp/py/ReadmeQuick_py.ts +9 -14
- package/project/.sdk/src/cmp/py/ReadmeRef_py.ts +23 -30
- package/project/.sdk/src/cmp/py/ReadmeTopQuick_py.ts +7 -12
- package/project/.sdk/src/cmp/py/ReadmeTopTest_py.ts +8 -7
- package/project/.sdk/src/cmp/py/fragment/EntityCreateOp.fragment.py +1 -1
- package/project/.sdk/src/cmp/py/fragment/EntityListOp.fragment.py +1 -1
- package/project/.sdk/src/cmp/py/fragment/EntityLoadOp.fragment.py +1 -1
- package/project/.sdk/src/cmp/py/fragment/EntityPatchOp.fragment.py +1 -1
- package/project/.sdk/src/cmp/py/fragment/EntityRemoveOp.fragment.py +1 -1
- package/project/.sdk/src/cmp/py/fragment/EntityUpdateOp.fragment.py +1 -1
- package/project/.sdk/src/cmp/rb/EntityOperation_rb.ts +3 -1
- package/project/.sdk/src/cmp/rb/EntityTypes_rb.ts +2 -1
- package/project/.sdk/src/cmp/rb/ReadmeEntity_rb.ts +4 -1
- package/project/.sdk/src/cmp/rb/ReadmeExamplesTest_rb.ts +3 -2
- package/project/.sdk/src/cmp/rb/ReadmeHowto_rb.ts +20 -10
- package/project/.sdk/src/cmp/rb/ReadmeModel_rb.ts +10 -9
- package/project/.sdk/src/cmp/rb/ReadmeQuick_rb.ts +6 -3
- package/project/.sdk/src/cmp/rb/ReadmeRef_rb.ts +14 -11
- package/project/.sdk/src/cmp/rb/ReadmeTopQuick_rb.ts +6 -3
- package/project/.sdk/src/cmp/rb/ReadmeTopTest_rb.ts +14 -10
- package/project/.sdk/src/cmp/rb/fragment/EntityCreateOp.fragment.rb +2 -1
- package/project/.sdk/src/cmp/rb/fragment/EntityListOp.fragment.rb +1 -1
- package/project/.sdk/src/cmp/rb/fragment/EntityLoadOp.fragment.rb +2 -1
- package/project/.sdk/src/cmp/rb/fragment/EntityPatchOp.fragment.rb +2 -1
- package/project/.sdk/src/cmp/rb/fragment/EntityRemoveOp.fragment.rb +2 -1
- package/project/.sdk/src/cmp/rb/fragment/EntityUpdateOp.fragment.rb +2 -1
- package/project/.sdk/src/cmp/rb/utility_rb.ts +10 -0
- package/project/.sdk/src/cmp/rust/ReadmeEntity_rust.ts +3 -2
- package/project/.sdk/src/cmp/rust/ReadmeHowto_rust.ts +6 -2
- package/project/.sdk/src/cmp/rust/ReadmeQuick_rust.ts +3 -2
- package/project/.sdk/src/cmp/rust/ReadmeRef_rust.ts +5 -4
- package/project/.sdk/src/cmp/rust/ReadmeTopQuick_rust.ts +3 -2
- package/project/.sdk/src/cmp/rust/ReadmeTopTest_rust.ts +7 -6
- package/project/.sdk/src/cmp/rust/utility_rust.ts +13 -1
- package/project/.sdk/src/cmp/scala/EntityTypes_scala.ts +5 -2
- package/project/.sdk/src/cmp/scala/ReadmeEntity_scala.ts +8 -19
- package/project/.sdk/src/cmp/scala/ReadmeHowto_scala.ts +17 -22
- package/project/.sdk/src/cmp/scala/ReadmeModel_scala.ts +10 -9
- package/project/.sdk/src/cmp/scala/ReadmeQuick_scala.ts +18 -28
- package/project/.sdk/src/cmp/scala/ReadmeRef_scala.ts +25 -35
- package/project/.sdk/src/cmp/scala/ReadmeTopQuick_scala.ts +14 -25
- package/project/.sdk/src/cmp/scala/ReadmeTopTest_scala.ts +16 -24
- package/project/.sdk/src/cmp/scala/utility_scala.ts +25 -0
- package/project/.sdk/src/cmp/swift/EntityTypes_swift.ts +1 -1
- package/project/.sdk/src/cmp/swift/ReadmeEntity_swift.ts +3 -2
- package/project/.sdk/src/cmp/swift/ReadmeHowto_swift.ts +22 -8
- package/project/.sdk/src/cmp/swift/ReadmeModel_swift.ts +10 -9
- package/project/.sdk/src/cmp/swift/ReadmeQuick_swift.ts +16 -8
- package/project/.sdk/src/cmp/swift/ReadmeRef_swift.ts +20 -15
- package/project/.sdk/src/cmp/swift/ReadmeTopQuick_swift.ts +12 -7
- package/project/.sdk/src/cmp/swift/ReadmeTopTest_swift.ts +21 -10
- package/project/.sdk/src/cmp/swift/utility_swift.ts +14 -1
- package/project/.sdk/src/cmp/ts/ReadmeEntity_ts.ts +2 -8
- package/project/.sdk/src/cmp/ts/ReadmeHowto_ts.ts +11 -9
- package/project/.sdk/src/cmp/ts/ReadmeModel_ts.ts +9 -9
- package/project/.sdk/src/cmp/ts/ReadmeQuick_ts.ts +7 -13
- package/project/.sdk/src/cmp/ts/ReadmeRef_ts.ts +14 -23
- package/project/.sdk/src/cmp/ts/ReadmeTopQuick_ts.ts +8 -14
- package/project/.sdk/src/cmp/ts/ReadmeTopTest_ts.ts +29 -14
- package/project/.sdk/src/cmp/ts/fragment/EntityCreateOp.fragment.ts +1 -1
- package/project/.sdk/src/cmp/ts/fragment/EntityListOp.fragment.ts +1 -1
- package/project/.sdk/src/cmp/ts/fragment/EntityLoadOp.fragment.ts +1 -1
- package/project/.sdk/src/cmp/ts/fragment/EntityPatchOp.fragment.ts +1 -1
- package/project/.sdk/src/cmp/ts/fragment/EntityUpdateOp.fragment.ts +1 -1
- package/project/.sdk/src/cmp/ts/utility_ts.ts +2 -1
- package/project/.sdk/src/cmp/zig/ReadmeEntity_zig.ts +3 -2
- package/project/.sdk/src/cmp/zig/ReadmeHowto_zig.ts +6 -2
- package/project/.sdk/src/cmp/zig/ReadmeQuick_zig.ts +4 -3
- package/project/.sdk/src/cmp/zig/ReadmeRef_zig.ts +5 -4
- package/project/.sdk/src/cmp/zig/ReadmeTopQuick_zig.ts +3 -2
- package/project/.sdk/src/cmp/zig/ReadmeTopTest_zig.ts +7 -6
- package/project/.sdk/src/cmp/zig/utility_zig.ts +13 -0
- package/project/.sdk/tm/c/utility/transform_request.c +1 -1
- package/project/.sdk/tm/clojure/src/sdk/core.clj +32 -26
- package/project/.sdk/tm/clojure/test/sdk/test/concurrency.clj +136 -0
- package/project/.sdk/tm/clojure/test/sdk/test_runner.clj +3 -1
- package/project/.sdk/tm/cpp/core/types.hpp +36 -6
- package/project/.sdk/tm/cpp/test/concurrency_test.cpp +348 -0
- package/project/.sdk/tm/cpp/utility/pipeline.hpp +42 -25
- package/project/.sdk/tm/csharp/core/Context.cs +8 -5
- package/project/.sdk/tm/csharp/test/ConcurrencyTest.cs +238 -0
- package/project/.sdk/tm/csharp/utility/Clean.cs +12 -10
- package/project/.sdk/tm/csharp/utility/TransformRequest.cs +1 -1
- package/project/.sdk/tm/elixir/lib/projectname/context.ex +3 -0
- package/project/.sdk/tm/elixir/lib/projectname/entity_base.ex +2 -1
- package/project/.sdk/tm/elixir/lib/projectname/utility.ex +31 -10
- package/project/.sdk/tm/elixir/test/concurrency_test.exs +176 -0
- package/project/.sdk/tm/go/core/context.go +5 -1
- package/project/.sdk/tm/go/test/concurrency_test.go +214 -0
- package/project/.sdk/tm/go/utility/transform_request.go +1 -1
- package/project/.sdk/tm/java/core/Context.java +6 -3
- package/project/.sdk/tm/java/test/ConcurrencyTest.java +221 -0
- package/project/.sdk/tm/java/utility/Clean.java +22 -17
- package/project/.sdk/tm/java/utility/TransformRequest.java +1 -1
- package/project/.sdk/tm/js/src/utility/TransformRequestUtility.js +1 -1
- package/project/.sdk/tm/kotlin/core/Context.kt +5 -3
- package/project/.sdk/tm/kotlin/test/ConcurrencyTest.kt +193 -0
- package/project/.sdk/tm/kotlin/utility/ResultTransform.kt +1 -1
- package/project/.sdk/tm/lua/utility/transform_request.lua +1 -1
- package/project/.sdk/tm/ocaml/Makefile +21 -8
- package/project/.sdk/tm/ocaml/sdk_runtime.ml +27 -12
- package/project/.sdk/tm/ocaml/sdk_types.ml +6 -2
- package/project/.sdk/tm/ocaml/test/concurrency_test.ml +148 -0
- package/project/.sdk/tm/perl/utility/transform_request.pm +1 -1
- package/project/.sdk/tm/php/feature/TestFeature.php +9 -9
- package/project/.sdk/tm/php/utility/TransformRequest.php +1 -1
- package/project/.sdk/tm/py/pkg/core/context.py +2 -2
- package/project/.sdk/tm/py/pkg/utility/clean.py +12 -8
- package/project/.sdk/tm/py/pkg/utility/transform_request.py +1 -1
- package/project/.sdk/tm/py/test/test_concurrency.py +177 -0
- package/project/.sdk/tm/rb/core/context.rb +8 -3
- package/project/.sdk/tm/rb/test/concurrency_test.rb +160 -0
- package/project/.sdk/tm/rb/utility/clean.rb +8 -7
- package/project/.sdk/tm/rb/utility/transform_request.rb +1 -1
- package/project/.sdk/tm/rust/utility/transform_request.rs +1 -1
- package/project/.sdk/tm/scala/Makefile +2 -0
- package/project/.sdk/tm/scala/core/Context.scala +6 -4
- package/project/.sdk/tm/scala/sdktest/ConcurrencyTest.scala +186 -0
- package/project/.sdk/tm/scala/utility/Transform.scala +1 -1
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/core/Context.swift +2 -0
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/ResultUtil.swift +35 -12
- package/project/.sdk/tm/swift/Tests/ProjectNameSDKTests/ConcurrencyTest.swift +214 -0
- package/project/.sdk/tm/ts/src/utility/TransformRequestUtility.ts +1 -1
- package/project/.sdk/tm/zig/core/utility.zig +1 -1
- package/project/.sdk/tm/zig/feature/test.zig +10 -8
- package/project/sdkgen-package.json +1 -1
- package/src/cmp/ReadmeErrors.ts +5 -5
- package/src/cmp/ReadmeExplanation.ts +4 -5
- package/src/cmp/ReadmeQuick.ts +3 -1
- package/src/cmp/ReadmeTop.ts +10 -12
- package/src/helpers/definitionPlan.ts +8 -4
- package/src/helpers/naming.ts +12 -4
- package/src/helpers/opExample.ts +97 -25
- package/src/helpers/opShape.ts +25 -9
- package/src/sdkgen.ts +14 -3
- package/src/utility.ts +31 -4
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# ProjectName SDK concurrency test
|
|
2
|
+
#
|
|
3
|
+
# Requests in flight at once on one client. Each resolves its operation
|
|
4
|
+
# through the cache the client's root context shares with every request, and
|
|
5
|
+
# registers and cleans secrets through the one registry the client holds.
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
import threading
|
|
9
|
+
|
|
10
|
+
from projectname_sdk import ProjectNameSDK
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
ROUNDS = 50
|
|
14
|
+
WIDTH = 8
|
|
15
|
+
OPS = 32
|
|
16
|
+
MASKED = "a [redacted] b [redacted] c"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Runs body on WIDTH threads released together, and returns what they raised.
|
|
20
|
+
def _at_once(body):
|
|
21
|
+
start = threading.Barrier(WIDTH)
|
|
22
|
+
raised = []
|
|
23
|
+
|
|
24
|
+
def run(n):
|
|
25
|
+
try:
|
|
26
|
+
start.wait()
|
|
27
|
+
body(n)
|
|
28
|
+
except BaseException as err:
|
|
29
|
+
raised.append(err)
|
|
30
|
+
|
|
31
|
+
threads = [threading.Thread(target=run, args=(n,)) for n in range(WIDTH)]
|
|
32
|
+
for thread in threads:
|
|
33
|
+
thread.start()
|
|
34
|
+
for thread in threads:
|
|
35
|
+
thread.join()
|
|
36
|
+
return raised
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# A live client whose transport answers at once.
|
|
40
|
+
def _live_client():
|
|
41
|
+
def fetch(url, fetchdef):
|
|
42
|
+
return {
|
|
43
|
+
"status": 200,
|
|
44
|
+
"statusText": "OK",
|
|
45
|
+
"headers": {},
|
|
46
|
+
"json": lambda: {"ok": True},
|
|
47
|
+
}, None
|
|
48
|
+
|
|
49
|
+
return ProjectNameSDK({
|
|
50
|
+
"base": "http://concurrency.test/api",
|
|
51
|
+
"allow": {"op": "direct"},
|
|
52
|
+
"system": {"fetch": fetch},
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _added_secret(rnd, n, k):
|
|
57
|
+
return "ADDED-SECRET-%d-%d-%d" % (rnd, n, k)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# Half the threads register their secrets; the rest run body(n) until those
|
|
61
|
+
# are done or body returns False. Returns what the threads raised.
|
|
62
|
+
def _while_registering(utility, root, rnd, body):
|
|
63
|
+
lock = threading.Lock()
|
|
64
|
+
registering = [WIDTH // 2]
|
|
65
|
+
registered = threading.Event()
|
|
66
|
+
|
|
67
|
+
def work(n):
|
|
68
|
+
if n < WIDTH // 2:
|
|
69
|
+
try:
|
|
70
|
+
for k in range(OPS):
|
|
71
|
+
utility.clean_add(root, _added_secret(rnd, n, k))
|
|
72
|
+
finally:
|
|
73
|
+
with lock:
|
|
74
|
+
registering[0] -= 1
|
|
75
|
+
if 0 == registering[0]:
|
|
76
|
+
registered.set()
|
|
77
|
+
return
|
|
78
|
+
while not registered.is_set():
|
|
79
|
+
if not body(n):
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
return _at_once(work)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# The first secret registered in the round that a clean leaves raw.
|
|
86
|
+
def _unmasked_secret(utility, root, rnd):
|
|
87
|
+
for n in range(WIDTH // 2):
|
|
88
|
+
for k in range(OPS):
|
|
89
|
+
added = _added_secret(rnd, n, k)
|
|
90
|
+
if "[redacted]" != utility.clean(root, added):
|
|
91
|
+
return added
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class TestConcurrency:
|
|
96
|
+
|
|
97
|
+
# Threads switch as often as the interpreter allows, so a race a few
|
|
98
|
+
# bytecodes wide has room to show.
|
|
99
|
+
def setup_method(self):
|
|
100
|
+
self._interval = sys.getswitchinterval()
|
|
101
|
+
sys.setswitchinterval(1e-6)
|
|
102
|
+
|
|
103
|
+
def teardown_method(self):
|
|
104
|
+
sys.setswitchinterval(self._interval)
|
|
105
|
+
|
|
106
|
+
def test_concurrent_resolutions_share_one_cached_operation(self):
|
|
107
|
+
for rnd in range(ROUNDS):
|
|
108
|
+
client = ProjectNameSDK.test(None, None)
|
|
109
|
+
utility = client.get_utility()
|
|
110
|
+
root = client.get_root_ctx()
|
|
111
|
+
ops = [[None] * OPS for _ in range(WIDTH)]
|
|
112
|
+
|
|
113
|
+
def resolve(n):
|
|
114
|
+
for k in range(OPS):
|
|
115
|
+
ops[n][k] = utility.make_context({"opname": "op%d" % k}, root).op
|
|
116
|
+
|
|
117
|
+
raised = _at_once(resolve)
|
|
118
|
+
|
|
119
|
+
assert [] == raised, "round %d raised: %r" % (rnd, raised)
|
|
120
|
+
for k in range(OPS):
|
|
121
|
+
cached = utility.make_context({"opname": "op%d" % k}, root).op
|
|
122
|
+
for n in range(WIDTH):
|
|
123
|
+
assert ops[n][k] is cached, \
|
|
124
|
+
"round %d: op%d resolved to more than one Operation" % (rnd, k)
|
|
125
|
+
|
|
126
|
+
# Secrets registered on some threads while others clean: every clean
|
|
127
|
+
# masks what was registered before it, the longer secret whole, and no
|
|
128
|
+
# registration is lost.
|
|
129
|
+
def test_concurrent_registration_keeps_every_secret_masked(self):
|
|
130
|
+
for rnd in range(ROUNDS // 4):
|
|
131
|
+
client = ProjectNameSDK.test(None, None)
|
|
132
|
+
utility = client.get_utility()
|
|
133
|
+
root = client.get_root_ctx()
|
|
134
|
+
inner = "INNER-SECRET-%d" % rnd
|
|
135
|
+
utility.clean_add(root, inner)
|
|
136
|
+
utility.clean_add(root, "OUTER-" + inner + "-TAIL")
|
|
137
|
+
text = "a " + inner + " b OUTER-" + inner + "-TAIL c"
|
|
138
|
+
assert MASKED == utility.clean(root, text)
|
|
139
|
+
|
|
140
|
+
wrong = []
|
|
141
|
+
|
|
142
|
+
def clean(n):
|
|
143
|
+
got = utility.clean(root, text)
|
|
144
|
+
if MASKED != got:
|
|
145
|
+
wrong.append(got)
|
|
146
|
+
return False
|
|
147
|
+
return True
|
|
148
|
+
|
|
149
|
+
raised = _while_registering(utility, root, rnd, clean)
|
|
150
|
+
|
|
151
|
+
assert [] == raised, "round %d raised: %r" % (rnd, raised)
|
|
152
|
+
assert [] == wrong, "round %d cleaned to: %r" % (rnd, wrong)
|
|
153
|
+
added = _unmasked_secret(utility, root, rnd)
|
|
154
|
+
assert added is None, "round %d: %s was registered but not masked" % (rnd, added)
|
|
155
|
+
|
|
156
|
+
# Requests on one client while secrets register on it: each request
|
|
157
|
+
# copies the client's options, the registry among them.
|
|
158
|
+
def test_concurrent_requests_survive_registration(self):
|
|
159
|
+
for rnd in range(ROUNDS // 4):
|
|
160
|
+
client = _live_client()
|
|
161
|
+
utility = client.get_utility()
|
|
162
|
+
root = client.get_root_ctx()
|
|
163
|
+
failed = []
|
|
164
|
+
|
|
165
|
+
def request(n):
|
|
166
|
+
res = client.direct({"path": "p%d" % n})
|
|
167
|
+
if True is not res.get("ok"):
|
|
168
|
+
failed.append(res)
|
|
169
|
+
return False
|
|
170
|
+
return True
|
|
171
|
+
|
|
172
|
+
raised = _while_registering(utility, root, rnd, request)
|
|
173
|
+
|
|
174
|
+
assert [] == raised, "round %d raised: %r" % (rnd, raised)
|
|
175
|
+
assert [] == failed, "round %d, a request failed: %r" % (rnd, failed)
|
|
176
|
+
added = _unmasked_secret(utility, root, rnd)
|
|
177
|
+
assert added is None, "round %d: %s was registered but not masked" % (rnd, added)
|
|
@@ -10,6 +10,10 @@ require_relative 'error'
|
|
|
10
10
|
require_relative 'helpers'
|
|
11
11
|
|
|
12
12
|
class ProjectNameContext
|
|
13
|
+
# Every context of a client shares its root's operation cache. CRuby's GVL
|
|
14
|
+
# makes each Hash operation atomic; a Ruby without one needs this lock.
|
|
15
|
+
OPMAP_LOCK = Mutex.new
|
|
16
|
+
|
|
13
17
|
attr_accessor :id, :out, :client, :utility, :ctrl, :meta, :config,
|
|
14
18
|
:entopts, :options, :entity, :shared, :opmap,
|
|
15
19
|
:data, :reqdata, :match, :reqmatch, :point,
|
|
@@ -83,7 +87,8 @@ class ProjectNameContext
|
|
|
83
87
|
# served to every subsequent entity's call.
|
|
84
88
|
entname = @entity&.respond_to?(:get_name) ? @entity.get_name : "_"
|
|
85
89
|
cache_key = "#{entname}:#{opname}"
|
|
86
|
-
|
|
90
|
+
cached = OPMAP_LOCK.synchronize { @opmap[cache_key] }
|
|
91
|
+
return cached if cached
|
|
87
92
|
return ProjectNameOperation.new({}) if opname.empty?
|
|
88
93
|
|
|
89
94
|
opcfg = VoxgigStruct.getpath(@config, "entity.#{entname}.op.#{opname}")
|
|
@@ -102,8 +107,8 @@ class ProjectNameContext
|
|
|
102
107
|
"input" => input,
|
|
103
108
|
"points" => points,
|
|
104
109
|
})
|
|
105
|
-
|
|
106
|
-
op
|
|
110
|
+
# Racing requests get the Operation stored first.
|
|
111
|
+
OPMAP_LOCK.synchronize { @opmap[cache_key] ||= op }
|
|
107
112
|
end
|
|
108
113
|
|
|
109
114
|
def make_error(code, msg)
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# ProjectName SDK concurrency test
|
|
2
|
+
#
|
|
3
|
+
# Requests in flight at once on one client. Each resolves its operation
|
|
4
|
+
# through the cache the client's root context shares with every request, and
|
|
5
|
+
# registers and cleans secrets through the one registry the client holds.
|
|
6
|
+
#
|
|
7
|
+
# The interpreter switches threads about every 100ms, so each test runs long
|
|
8
|
+
# enough to be switched in the middle of a resolution or a registration.
|
|
9
|
+
|
|
10
|
+
require "minitest/autorun"
|
|
11
|
+
require_relative "../ProjectName_sdk"
|
|
12
|
+
|
|
13
|
+
class ConcurrencyTest < Minitest::Test
|
|
14
|
+
ROUNDS = 3
|
|
15
|
+
WIDTH = 8
|
|
16
|
+
OPS = 6000
|
|
17
|
+
SEEDED = 1000
|
|
18
|
+
ADDED = 400
|
|
19
|
+
MASKED = "a [redacted] b [redacted] c"
|
|
20
|
+
|
|
21
|
+
# Runs the block on WIDTH threads, released once every one is waiting, and
|
|
22
|
+
# returns what they raised.
|
|
23
|
+
def at_once
|
|
24
|
+
lock = Mutex.new
|
|
25
|
+
waiting = 0
|
|
26
|
+
ready = Queue.new
|
|
27
|
+
gate = Queue.new
|
|
28
|
+
seen = Queue.new
|
|
29
|
+
raised = Queue.new
|
|
30
|
+
threads = (0...WIDTH).map do |n|
|
|
31
|
+
Thread.new do
|
|
32
|
+
lock.synchronize { waiting += 1 }
|
|
33
|
+
ready << n
|
|
34
|
+
gate.pop
|
|
35
|
+
seen << lock.synchronize { waiting }
|
|
36
|
+
yield n
|
|
37
|
+
rescue Exception => e
|
|
38
|
+
raised << e
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
WIDTH.times { ready.pop }
|
|
42
|
+
WIDTH.times { gate << true }
|
|
43
|
+
threads.each(&:join)
|
|
44
|
+
released = Array.new(seen.size) { seen.pop }
|
|
45
|
+
assert_equal [WIDTH] * WIDTH, released, "threads released before #{WIDTH} were waiting"
|
|
46
|
+
Array.new(raised.size) { raised.pop }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def test_concurrent_resolutions_share_one_cached_operation
|
|
50
|
+
ROUNDS.times do |round|
|
|
51
|
+
client = ProjectNameSDK.test(nil, nil)
|
|
52
|
+
utility = client.get_utility
|
|
53
|
+
root = client.get_root_ctx
|
|
54
|
+
ops = Array.new(WIDTH) { Array.new(OPS) }
|
|
55
|
+
|
|
56
|
+
raised = at_once do |n|
|
|
57
|
+
OPS.times { |k| ops[n][k] = utility.make_context.call({ "opname" => "op#{k}" }, root).op }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
assert_empty raised, "round #{round} raised: #{raised.inspect}"
|
|
61
|
+
OPS.times do |k|
|
|
62
|
+
cached = utility.make_context.call({ "opname" => "op#{k}" }, root).op
|
|
63
|
+
WIDTH.times do |n|
|
|
64
|
+
assert_same cached, ops[n][k], "round #{round}: op#{k} resolved to more than one Operation"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# A live client whose transport answers at once.
|
|
71
|
+
def live_client
|
|
72
|
+
ProjectNameSDK.new({
|
|
73
|
+
"base" => "http://concurrency.test/api",
|
|
74
|
+
"allow" => { "op" => "direct" },
|
|
75
|
+
"system" => {
|
|
76
|
+
"fetch" => lambda { |_url, _fetchdef|
|
|
77
|
+
[{ "status" => 200, "statusText" => "OK", "headers" => {},
|
|
78
|
+
"json" => lambda { { "ok" => true } } }, nil]
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
})
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Half the threads register their secrets; the rest run body until those
|
|
85
|
+
# are done or it returns false. Returns what the threads raised.
|
|
86
|
+
def while_registering(utility, root, &body)
|
|
87
|
+
lock = Mutex.new
|
|
88
|
+
registering = WIDTH / 2
|
|
89
|
+
at_once do |n|
|
|
90
|
+
if n < WIDTH / 2
|
|
91
|
+
begin
|
|
92
|
+
ADDED.times { |k| utility.clean_add.call(root, "ADDED-SECRET-#{n}-#{k}") }
|
|
93
|
+
ensure
|
|
94
|
+
lock.synchronize { registering -= 1 }
|
|
95
|
+
end
|
|
96
|
+
else
|
|
97
|
+
while 0 < lock.synchronize { registering }
|
|
98
|
+
break unless body.call(n)
|
|
99
|
+
Thread.pass
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def assert_every_secret_masked(utility, root)
|
|
106
|
+
(WIDTH / 2).times do |n|
|
|
107
|
+
ADDED.times do |k|
|
|
108
|
+
added = "ADDED-SECRET-#{n}-#{k}"
|
|
109
|
+
assert_equal "[redacted]", utility.clean.call(root, added), "#{added} was registered but not masked"
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Secrets registered on some threads while others clean: every clean masks
|
|
115
|
+
# what was registered before it, the longer secret whole, and no
|
|
116
|
+
# registration is lost.
|
|
117
|
+
def test_concurrent_registration_keeps_every_secret_masked
|
|
118
|
+
client = ProjectNameSDK.test(nil, nil)
|
|
119
|
+
utility = client.get_utility
|
|
120
|
+
root = client.get_root_ctx
|
|
121
|
+
SEEDED.times { |k| utility.clean_add.call(root, "SEEDED-SECRET-#{k}") }
|
|
122
|
+
utility.clean_add.call(root, "INNER-SECRET")
|
|
123
|
+
utility.clean_add.call(root, "OUTER-INNER-SECRET-TAIL")
|
|
124
|
+
text = "a INNER-SECRET b OUTER-INNER-SECRET-TAIL c"
|
|
125
|
+
assert_equal MASKED, utility.clean.call(root, text)
|
|
126
|
+
|
|
127
|
+
wrong = Queue.new
|
|
128
|
+
raised = while_registering(utility, root) do
|
|
129
|
+
got = utility.clean.call(root, text)
|
|
130
|
+
wrong << got if MASKED != got
|
|
131
|
+
MASKED == got
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
cleaned = Array.new(wrong.size) { wrong.pop }
|
|
135
|
+
assert_empty raised, "raised: #{raised.inspect}"
|
|
136
|
+
assert_empty cleaned, "cleaned to: #{cleaned.inspect}"
|
|
137
|
+
assert_every_secret_masked(utility, root)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Requests on one client while secrets register on it: each request copies
|
|
141
|
+
# the client's options, the registry among them.
|
|
142
|
+
def test_concurrent_requests_survive_registration
|
|
143
|
+
client = live_client
|
|
144
|
+
utility = client.get_utility
|
|
145
|
+
root = client.get_root_ctx
|
|
146
|
+
SEEDED.times { |k| utility.clean_add.call(root, "SEEDED-SECRET-#{k}") }
|
|
147
|
+
|
|
148
|
+
failed = Queue.new
|
|
149
|
+
raised = while_registering(utility, root) do |n|
|
|
150
|
+
res = client.direct({ "path" => "p#{n}" })
|
|
151
|
+
failed << res unless true == res["ok"]
|
|
152
|
+
true == res["ok"]
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
requests = Array.new(failed.size) { failed.pop }
|
|
156
|
+
assert_empty raised, "raised: #{raised.inspect}"
|
|
157
|
+
assert_empty requests, "a request failed: #{requests.inspect}"
|
|
158
|
+
assert_every_secret_masked(utility, root)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
@@ -83,19 +83,20 @@ module ProjectNameUtilities
|
|
|
83
83
|
out
|
|
84
84
|
end
|
|
85
85
|
|
|
86
|
+
# Requests on other threads clean while one registers, so a registration
|
|
87
|
+
# publishes a new list and never changes a published one.
|
|
88
|
+
REGISTERING = Mutex.new
|
|
89
|
+
|
|
86
90
|
# Register a secret value. Idempotent; shorter than `min` is not a secret
|
|
87
91
|
# the SDK can mask without blanking ordinary text.
|
|
88
92
|
def self.add(ctx, value)
|
|
89
93
|
cfg = config(ctx)
|
|
90
94
|
return unless value.is_a?(String) && value.length >= cfg["min"]
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
values << form
|
|
96
|
-
changed = true
|
|
95
|
+
REGISTERING.synchronize do
|
|
96
|
+
values = cfg["values"]
|
|
97
|
+
added = forms(value).reject { |form| form.length < cfg["min"] || values.include?(form) }
|
|
98
|
+
cfg["values"] = (values + added).sort_by { |v| -v.length } unless added.empty?
|
|
97
99
|
end
|
|
98
|
-
values.sort_by! { |v| -v.length } if changed
|
|
99
100
|
nil
|
|
100
101
|
end
|
|
101
102
|
|
|
@@ -11,7 +11,7 @@ module ProjectNameUtilities
|
|
|
11
11
|
|
|
12
12
|
# A header, cookie or query argument travels where PrepareHeaders or
|
|
13
13
|
# PrepareQuery sends it, so the body is built from the request data without
|
|
14
|
-
# it, unless the
|
|
14
|
+
# it, unless the point marks it as a field the body keeps.
|
|
15
15
|
def self.routed_arg_names(ctx)
|
|
16
16
|
(call_args(ctx, "header") + call_args(ctx, "cookie") + call_args(ctx, "query")).map(&:first)
|
|
17
17
|
.reject { |name| field_arg?(ctx, name) }
|
|
@@ -14,7 +14,7 @@ fn strip_action(reqdata: Value) -> Value {
|
|
|
14
14
|
|
|
15
15
|
// A header, cookie or query argument travels where prepare_headers_util or
|
|
16
16
|
// prepare_query_util sends it, so the body is built from the request data
|
|
17
|
-
// without it, unless the
|
|
17
|
+
// without it, unless the point marks it as a field the body keeps.
|
|
18
18
|
fn routed_arg_names(ctx: &Rc<Context>) -> Vec<String> {
|
|
19
19
|
let mut names = Vec::new();
|
|
20
20
|
for kind in ["header", "cookie", "query"] {
|
|
@@ -18,12 +18,14 @@ build:
|
|
|
18
18
|
# and so runs in every scala SDK, model-activated or not. SdkCleanTestMain is
|
|
19
19
|
# the canary sweep: no credential leaves the SDK in any form. SdkServerTestMain
|
|
20
20
|
# checks how a templated base URL takes its server variables.
|
|
21
|
+
# SdkConcurrencyTestMain races first requests on one client.
|
|
21
22
|
test:
|
|
22
23
|
scala-cli run . --main-class OmniSmoke
|
|
23
24
|
scala-cli run . --main-class SdkTestMain
|
|
24
25
|
scala-cli run . --main-class SdkEntityTestMain
|
|
25
26
|
scala-cli run . --main-class SdkCleanTestMain
|
|
26
27
|
scala-cli run . --main-class SdkServerTestMain
|
|
28
|
+
scala-cli run . --main-class SdkConcurrencyTestMain
|
|
27
29
|
scala-cli run . --main-class SecretsTestMain
|
|
28
30
|
scala-cli run . --main-class Runner -- ../.sdk/test/test.json
|
|
29
31
|
scala-cli run . --main-class PrimaryCorpusMain -- ../.sdk/test/test.json
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
package SCALAPACKAGE.core
|
|
2
2
|
|
|
3
3
|
import java.util.{ArrayList, LinkedHashMap, List => JList, Map => JMap}
|
|
4
|
-
import java.util.concurrent.ThreadLocalRandom
|
|
4
|
+
import java.util.concurrent.{ConcurrentHashMap, ThreadLocalRandom}
|
|
5
5
|
import SCALAPACKAGE.utility.struct.Struct
|
|
6
6
|
|
|
7
7
|
// Per-operation context threaded through the pipeline and feature hooks.
|
|
@@ -85,7 +85,7 @@ class Context(ctxmap: JMap[String, Object], basectx: Context) {
|
|
|
85
85
|
// Opmap
|
|
86
86
|
Helpers.getCtxProp(ctxmap, "opmap") match { case om: JMap[_, _] => opmap = om.asInstanceOf[JMap[String, Operation]]; case _ => }
|
|
87
87
|
if (opmap == null && basectx != null) opmap = basectx.opmap
|
|
88
|
-
if (opmap == null) opmap = new
|
|
88
|
+
if (opmap == null) opmap = new ConcurrentHashMap[String, Operation]()
|
|
89
89
|
|
|
90
90
|
// Data
|
|
91
91
|
data = Helpers.toMapAny(Helpers.getCtxProp(ctxmap, "data"))
|
|
@@ -153,8 +153,10 @@ class Context(ctxmap: JMap[String, Object], basectx: Context) {
|
|
|
153
153
|
opdef.put("points", points)
|
|
154
154
|
|
|
155
155
|
val newop = new Operation(opdef)
|
|
156
|
-
|
|
157
|
-
|
|
156
|
+
// Requests on other threads share this cache; every one of them gets
|
|
157
|
+
// the Operation stored first.
|
|
158
|
+
val stored = opmap.putIfAbsent(cacheKey, newop)
|
|
159
|
+
if (stored != null) stored else newop
|
|
158
160
|
}
|
|
159
161
|
|
|
160
162
|
def makeError(code: String, msg: String): SdkError = new SdkError(code, msg, this)
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// Requests in flight at once on one client. Each resolves its operation
|
|
2
|
+
// through the cache the client's root context shares with every request, and
|
|
3
|
+
// registers and cleans secrets through the one registry the client holds.
|
|
4
|
+
|
|
5
|
+
import java.util.concurrent.{ConcurrentLinkedQueue, CountDownLatch, CyclicBarrier}
|
|
6
|
+
import java.util.function.{BiFunction, Supplier}
|
|
7
|
+
import java.util.{Map => JMap}
|
|
8
|
+
|
|
9
|
+
import SCALAPACKAGE.core.{Context, Operation, ProjectNameSDK, Utility}
|
|
10
|
+
|
|
11
|
+
object SdkConcurrencyTestMain {
|
|
12
|
+
|
|
13
|
+
private val Rounds = 200
|
|
14
|
+
private val Width = 8
|
|
15
|
+
private val Ops = 32
|
|
16
|
+
|
|
17
|
+
private def om(kv: (String, Object)*): JMap[String, Object] = SdkTestSupport.om(kv*)
|
|
18
|
+
|
|
19
|
+
// A live client whose transport answers at once.
|
|
20
|
+
private def liveClient(): ProjectNameSDK = {
|
|
21
|
+
val json: Supplier[Object] = () => om("ok" -> SdkTestSupport.B(true))
|
|
22
|
+
val fetch: BiFunction[String, JMap[String, Object], Object] = (_, _) =>
|
|
23
|
+
om("status" -> SdkTestSupport.I(200), "statusText" -> "OK", "headers" -> om(), "json" -> json)
|
|
24
|
+
new ProjectNameSDK(om(
|
|
25
|
+
"base" -> "http://concurrency.test/api",
|
|
26
|
+
"allow" -> om("op" -> "direct"),
|
|
27
|
+
"system" -> om("fetch" -> fetch)))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Runs body on Width threads released together, and returns what they threw.
|
|
31
|
+
private def atOnce(body: Int => Unit): List[Throwable] = {
|
|
32
|
+
val start = new CyclicBarrier(Width)
|
|
33
|
+
val thrown = new ConcurrentLinkedQueue[Throwable]()
|
|
34
|
+
val threads = (0 until Width).map { n =>
|
|
35
|
+
val runner: Runnable = () =>
|
|
36
|
+
try {
|
|
37
|
+
start.await()
|
|
38
|
+
body(n)
|
|
39
|
+
}
|
|
40
|
+
catch { case e: Throwable => thrown.add(e); () }
|
|
41
|
+
val thread = new Thread(runner)
|
|
42
|
+
thread.start()
|
|
43
|
+
thread
|
|
44
|
+
}
|
|
45
|
+
threads.foreach(_.join())
|
|
46
|
+
thrown.toArray(new Array[Throwable](0)).toList
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private def addedSecret(round: Int, n: Int, k: Int): String =
|
|
50
|
+
"ADDED-SECRET-" + round + "-" + n + "-" + k
|
|
51
|
+
|
|
52
|
+
// Registers thread n's secrets, then counts the thread out of registering.
|
|
53
|
+
private def registerSecrets(utility: Utility, root: Context, round: Int, n: Int,
|
|
54
|
+
registering: CountDownLatch): Unit =
|
|
55
|
+
try {
|
|
56
|
+
for (k <- 0 until Ops) utility.cleanAdd(root, addedSecret(round, n, k))
|
|
57
|
+
}
|
|
58
|
+
finally registering.countDown()
|
|
59
|
+
|
|
60
|
+
// The first secret registered in the round that a clean leaves raw.
|
|
61
|
+
private def unmasked(utility: Utility, root: Context, round: Int): Option[String] =
|
|
62
|
+
(for (n <- 0 until Width / 2; k <- 0 until Ops) yield addedSecret(round, n, k))
|
|
63
|
+
.find(added => "[redacted]" != utility.clean(root, added))
|
|
64
|
+
|
|
65
|
+
// The first failure across the rounds, or null.
|
|
66
|
+
private def firstFailure(round: Int => String): String = {
|
|
67
|
+
var failure: String = null
|
|
68
|
+
var n = 0
|
|
69
|
+
while (failure == null && n < Rounds) {
|
|
70
|
+
failure = round(n)
|
|
71
|
+
n += 1
|
|
72
|
+
}
|
|
73
|
+
failure
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
def main(args: Array[String]): Unit = {
|
|
77
|
+
val rep = new SdkTestReport()
|
|
78
|
+
|
|
79
|
+
rep.scope("concurrency.requests") {
|
|
80
|
+
val failure = firstFailure { round =>
|
|
81
|
+
// A fresh client each round, so every request in it is a first request.
|
|
82
|
+
val client = liveClient()
|
|
83
|
+
val results = new Array[JMap[String, Object]](Width)
|
|
84
|
+
val thrown = atOnce(n => results(n) = client.direct(om("path" -> ("p" + n))))
|
|
85
|
+
val failed = (0 until Width).find(n =>
|
|
86
|
+
results(n) == null || java.lang.Boolean.TRUE != results(n).get("ok"))
|
|
87
|
+
if (thrown.nonEmpty) "round " + round + " threw: " + thrown
|
|
88
|
+
else failed.map(n => "round " + round + ", request " + n + " failed: " + results(n)).orNull
|
|
89
|
+
}
|
|
90
|
+
rep.check("concurrency.requests", failure == null, failure)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
rep.scope("concurrency.operations") {
|
|
94
|
+
val failure = firstFailure { round =>
|
|
95
|
+
val client = liveClient()
|
|
96
|
+
val utility = client.getUtility()
|
|
97
|
+
val root = client.getRootCtx()
|
|
98
|
+
val got = Array.ofDim[Operation](Width, Ops)
|
|
99
|
+
val thrown = atOnce { n =>
|
|
100
|
+
for (k <- 0 until Ops) {
|
|
101
|
+
got(n)(k) = utility.makeContext(om("opname" -> ("op" + k)), root).op
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
val split = (0 until Ops).find { k =>
|
|
105
|
+
val cached = utility.makeContext(om("opname" -> ("op" + k)), root).op
|
|
106
|
+
(0 until Width).exists(n => !(got(n)(k) eq cached))
|
|
107
|
+
}
|
|
108
|
+
if (thrown.nonEmpty) "round " + round + " threw: " + thrown
|
|
109
|
+
else split.map(k => "round " + round + ": op" + k + " resolved to more than one Operation").orNull
|
|
110
|
+
}
|
|
111
|
+
rep.check("concurrency.operations", failure == null, failure)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Secrets registered on some threads while others clean: every clean
|
|
115
|
+
// masks what was registered before it, the longer secret whole, and no
|
|
116
|
+
// registration is lost.
|
|
117
|
+
rep.scope("concurrency.registry") {
|
|
118
|
+
val masked = "a [redacted] b [redacted] c"
|
|
119
|
+
val failure = firstFailure { round =>
|
|
120
|
+
if (round >= Rounds / 4) null
|
|
121
|
+
else {
|
|
122
|
+
val client = liveClient()
|
|
123
|
+
val utility = client.getUtility()
|
|
124
|
+
val root = client.getRootCtx()
|
|
125
|
+
val inner = "INNER-SECRET-" + round
|
|
126
|
+
utility.cleanAdd(root, inner)
|
|
127
|
+
utility.cleanAdd(root, "OUTER-" + inner + "-TAIL")
|
|
128
|
+
val text = "a " + inner + " b OUTER-" + inner + "-TAIL c"
|
|
129
|
+
val before = utility.clean(root, text)
|
|
130
|
+
val registering = new CountDownLatch(Width / 2)
|
|
131
|
+
val wrong = new ConcurrentLinkedQueue[Object]()
|
|
132
|
+
val thrown = atOnce { n =>
|
|
133
|
+
if (n < Width / 2) registerSecrets(utility, root, round, n, registering)
|
|
134
|
+
else {
|
|
135
|
+
while (0L < registering.getCount) {
|
|
136
|
+
val got = utility.clean(root, text)
|
|
137
|
+
if (masked != got) wrong.add(got)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (masked != before) "round " + round + " cleaned to: " + before
|
|
142
|
+
else if (thrown.nonEmpty) "round " + round + " threw: " + thrown
|
|
143
|
+
else if (!wrong.isEmpty) "round " + round + " cleaned to: " + wrong
|
|
144
|
+
else unmasked(utility, root, round)
|
|
145
|
+
.map(added => "round " + round + ": " + added + " was registered but not masked").orNull
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
rep.check("concurrency.registry", failure == null, failure)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Requests on one client while secrets register on it: each request
|
|
152
|
+
// copies the client's options, the registry among them.
|
|
153
|
+
rep.scope("concurrency.registry.requests") {
|
|
154
|
+
val failure = firstFailure { round =>
|
|
155
|
+
if (round >= Rounds / 4) null
|
|
156
|
+
else {
|
|
157
|
+
val client = liveClient()
|
|
158
|
+
val utility = client.getUtility()
|
|
159
|
+
val root = client.getRootCtx()
|
|
160
|
+
val registering = new CountDownLatch(Width / 2)
|
|
161
|
+
val failed = new ConcurrentLinkedQueue[Object]()
|
|
162
|
+
val thrown = atOnce { n =>
|
|
163
|
+
if (n < Width / 2) registerSecrets(utility, root, round, n, registering)
|
|
164
|
+
else {
|
|
165
|
+
var going = true
|
|
166
|
+
while (going && 0L < registering.getCount) {
|
|
167
|
+
val res = client.direct(om("path" -> ("p" + n)))
|
|
168
|
+
if (java.lang.Boolean.TRUE != res.get("ok")) {
|
|
169
|
+
failed.add(res)
|
|
170
|
+
going = false
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (thrown.nonEmpty) "round " + round + " threw: " + thrown
|
|
176
|
+
else if (!failed.isEmpty) "round " + round + ", a request failed: " + failed
|
|
177
|
+
else unmasked(utility, root, round)
|
|
178
|
+
.map(added => "round " + round + ": " + added + " was registered but not masked").orNull
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
rep.check("concurrency.registry.requests", failure == null, failure)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
rep.finish("CONCURRENCY")
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -28,7 +28,7 @@ object TransformRequest {
|
|
|
28
28
|
|
|
29
29
|
// A header, cookie or query argument travels where PrepareHeaders or
|
|
30
30
|
// PrepareQuery sends it, so the body is built from the request data without
|
|
31
|
-
// it, unless the
|
|
31
|
+
// it, unless the point marks it as a field the body keeps.
|
|
32
32
|
private def routedArgNames(ctx: Context): Seq[String] =
|
|
33
33
|
(Param.callArgs(ctx, "header") ++ Param.callArgs(ctx, "cookie") ++ Param.callArgs(ctx, "query")).map(_._1)
|
|
34
34
|
.filterNot(name => fieldArg(ctx, name))
|
|
@@ -150,6 +150,8 @@ public final class Context {
|
|
|
150
150
|
opm.entries["input"] = .string(input)
|
|
151
151
|
opm.entries["points"] = .list(points)
|
|
152
152
|
|
|
153
|
+
// Each context holds its own copy of the cache, so a request racing
|
|
154
|
+
// another builds its own Operation, a read-only descriptor.
|
|
153
155
|
let op = Operation(opm)
|
|
154
156
|
opmap[cacheKey] = op
|
|
155
157
|
return op
|