Ensure serialization is usable from K/N background thread

* fix concurrency issue when accessing C2TC

This bytearray was mutating itself during the init phase. By moving it to an object, the init {} deals with the initialization and makes it thread safe. The same logic was used for ESCAPE_2_CHAR.

* Serialization meets K/N background threads

    * Run all tests from both main and bg threads
    * Update Gradle distribution from bin to all
    * Update JsonReader, get rid of companions and late-initialization to make it K/N friendly and produce less code
    * Update tests to be bg-friendly

Co-authored-by: Mohamed Zenadi <mohamed@zenadi.com>
diff --git a/build.gradle b/build.gradle
index 83346e9..9d94ad8 100644
--- a/build.gradle
+++ b/build.gradle
@@ -91,6 +91,7 @@
 
 // To make it visible for compiler-version.gradle
 ext.compilerVersion = org.jetbrains.kotlin.config.KotlinCompilerVersion.VERSION
+ext.nativeDebugBuild = org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType.DEBUG
 apply plugin: 'binary-compatibility-validator'
 
 apiValidation {
@@ -184,4 +185,4 @@
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/core/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt b/core/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
index dedc879..393e2b5 100644
--- a/core/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
+++ b/core/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
@@ -5,6 +5,7 @@
 package kotlinx.serialization
 
 import kotlinx.serialization.modules.*
+import kotlin.native.concurrent.*
 
 @Serializable
 open class PolyBase(val id: Int) {
@@ -29,6 +30,7 @@
 @Serializable
 data class PolyDerived(val s: String) : PolyBase(1)
 
+@SharedImmutable
 val BaseAndDerivedModule = SerializersModule {
     polymorphic(PolyBase::class, PolyBase.serializer()) {
         subclass(PolyDerived.serializer())
diff --git a/core/nativeTest/src/kotlinx/serialization/test/CurrentPlatform.kt b/core/nativeTest/src/kotlinx/serialization/test/CurrentPlatform.kt
index 2691ce0..9e51d7f 100644
--- a/core/nativeTest/src/kotlinx/serialization/test/CurrentPlatform.kt
+++ b/core/nativeTest/src/kotlinx/serialization/test/CurrentPlatform.kt
@@ -5,5 +5,7 @@
 package kotlinx.serialization.test
 
 import kotlinx.serialization.test.Platform
+import kotlin.native.concurrent.SharedImmutable
 
+@SharedImmutable
 public actual val currentPlatform: Platform = Platform.NATIVE
diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt b/formats/cbor/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
index 00376dc..86aafe4 100644
--- a/formats/cbor/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
+++ b/formats/cbor/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
@@ -5,6 +5,7 @@
 package kotlinx.serialization
 
 import kotlinx.serialization.modules.*
+import kotlin.native.concurrent.*
 
 @Serializable
 abstract class SimpleAbstract
@@ -18,6 +19,7 @@
 @Serializable
 data class PolyBox(@Polymorphic val boxed: SimpleAbstract)
 
+@SharedImmutable
 val SimplePolymorphicModule = SerializersModule {
     polymorphic(SimpleAbstract::class) {
         subclass(SimpleIntInheritor.serializer())
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonReader.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonReader.kt
index 14c4a96..b1f8208 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonReader.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonReader.kt
@@ -4,9 +4,9 @@
 
 package kotlinx.serialization.json.internal
 
-import kotlinx.serialization.json.internal.EscapeCharMappings.ESCAPE_2_CHAR
+import kotlinx.serialization.json.internal.CharMappings.C2TC
+import kotlinx.serialization.json.internal.CharMappings.ESCAPE_2_CHAR
 import kotlin.jvm.*
-import kotlin.native.concurrent.*
 
 internal const val lenientHint = "Use 'isLenient = true' in 'Json {}` builder to accept non-compliant JSON."
 internal const val coerceInputValuesHint = "Use 'coerceInputValues = true' in 'Json {}` builder to coerce nulls to default values."
@@ -51,32 +51,20 @@
 // mapping from escape chars real chars
 private const val ESC2C_MAX = 0x75
 
-@SharedImmutable
-internal val C2TC = ByteArray(CTC_MAX).apply {
-    for (i in 0..0x20) {
-        initC2TC(i, TC_INVALID)
-    }
-
-    initC2TC(0x09, TC_WS)
-    initC2TC(0x0a, TC_WS)
-    initC2TC(0x0d, TC_WS)
-    initC2TC(0x20, TC_WS)
-    initC2TC(COMMA, TC_COMMA)
-    initC2TC(COLON, TC_COLON)
-    initC2TC(BEGIN_OBJ, TC_BEGIN_OBJ)
-    initC2TC(END_OBJ, TC_END_OBJ)
-    initC2TC(BEGIN_LIST, TC_BEGIN_LIST)
-    initC2TC(END_LIST, TC_END_LIST)
-    initC2TC(STRING, TC_STRING)
-    initC2TC(STRING_ESC, TC_STRING_ESC)
-}
-
-// object instead of @SharedImmutable because there is mutual initialization in [initC2ESC]
-internal object EscapeCharMappings {
+// object instead of @SharedImmutable because there is mutual initialization in [initC2ESC] and [initC2TC]
+internal object CharMappings {
     @JvmField
-    public val ESCAPE_2_CHAR = CharArray(ESC2C_MAX)
+    val ESCAPE_2_CHAR = CharArray(ESC2C_MAX)
+
+    @JvmField
+    val C2TC = ByteArray(CTC_MAX)
 
     init {
+        initEscape()
+        initCharToToken()
+    }
+
+    private fun initEscape() {
         for (i in 0x00..0x1f) {
             initC2ESC(i, UNICODE_ESC)
         }
@@ -91,19 +79,36 @@
         initC2ESC(STRING_ESC, STRING_ESC)
     }
 
+    private fun initCharToToken() {
+        for (i in 0..0x20) {
+            initC2TC(i, TC_INVALID)
+        }
+
+        initC2TC(0x09, TC_WS)
+        initC2TC(0x0a, TC_WS)
+        initC2TC(0x0d, TC_WS)
+        initC2TC(0x20, TC_WS)
+        initC2TC(COMMA, TC_COMMA)
+        initC2TC(COLON, TC_COLON)
+        initC2TC(BEGIN_OBJ, TC_BEGIN_OBJ)
+        initC2TC(END_OBJ, TC_END_OBJ)
+        initC2TC(BEGIN_LIST, TC_BEGIN_LIST)
+        initC2TC(END_LIST, TC_END_LIST)
+        initC2TC(STRING, TC_STRING)
+        initC2TC(STRING_ESC, TC_STRING_ESC)
+    }
+
     private fun initC2ESC(c: Int, esc: Char) {
         if (esc != UNICODE_ESC) ESCAPE_2_CHAR[esc.toInt()] = c.toChar()
     }
 
     private fun initC2ESC(c: Char, esc: Char) = initC2ESC(c.toInt(), esc)
-}
 
-private fun ByteArray.initC2TC(c: Int, cl: Byte) {
-    this[c] = cl
-}
+    private fun initC2TC(c: Int, cl: Byte) {
+        C2TC[c] = cl
+    }
 
-private fun ByteArray.initC2TC(c: Char, cl: Byte) {
-    initC2TC(c.toInt(), cl)
+    private fun initC2TC(c: Char, cl: Byte) = initC2TC(c.toInt(), cl)
 }
 
 internal fun charToTokenClass(c: Char) = if (c.toInt() < CTC_MAX) C2TC[c.toInt()] else TC_OTHER
diff --git a/formats/json/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt b/formats/json/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
index 88b209d..5aaf628 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
+++ b/formats/json/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
@@ -6,6 +6,7 @@
 
 import kotlinx.serialization.json.*
 import kotlinx.serialization.modules.*
+import kotlin.native.concurrent.*
 
 @Serializable
 open class PolyBase(val id: Int) {
@@ -34,6 +35,7 @@
 @Serializable
 data class PolyDerived(val s: String) : PolyBase(1)
 
+@SharedImmutable
 val BaseAndDerivedModule = SerializersModule {
     polymorphic(PolyBase::class, PolyBase.serializer()) {
         subclass(PolyDerived.serializer())
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/PolymorphicClasses.kt b/formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/PolymorphicClasses.kt
index 3bd5f95..e46de17 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/PolymorphicClasses.kt
+++ b/formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/PolymorphicClasses.kt
@@ -7,6 +7,7 @@
 import kotlinx.serialization.*
 import kotlinx.serialization.json.*
 import kotlinx.serialization.modules.*
+import kotlin.native.concurrent.*
 
 @Serializable
 internal open class InnerBase
@@ -37,7 +38,7 @@
 @Serializable
 internal data class OuterNullableBox(@Polymorphic val outerBase: OuterBase?, @Polymorphic val innerBase: InnerBase?)
 
-
+@SharedImmutable
 internal val polymorphicTestModule = SerializersModule {
     polymorphic(InnerBase::class) {
         subclass(InnerImpl.serializer())
@@ -50,11 +51,13 @@
     }
 }
 
+@SharedImmutable
 internal val polymorphicJson = Json {
     serializersModule = polymorphicTestModule
     encodeDefaults = true
 }
 
+@SharedImmutable
 internal val polymorphicRelaxedJson = Json {
     isLenient = true
     serializersModule = polymorphicTestModule
diff --git a/formats/json/nativeTest/src/kotlinx/serialization/test/CurrentPlatform.kt b/formats/json/nativeTest/src/kotlinx/serialization/test/CurrentPlatform.kt
index 5824904..32806c1 100644
--- a/formats/json/nativeTest/src/kotlinx/serialization/test/CurrentPlatform.kt
+++ b/formats/json/nativeTest/src/kotlinx/serialization/test/CurrentPlatform.kt
@@ -4,5 +4,8 @@
 
 package kotlinx.serialization.test
 
+import kotlin.native.concurrent.SharedImmutable
 
+
+@SharedImmutable
 public actual val currentPlatform: Platform = Platform.NATIVE
diff --git a/formats/protobuf/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt b/formats/protobuf/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
index 86ad325..7ef9729 100644
--- a/formats/protobuf/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
+++ b/formats/protobuf/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
@@ -6,6 +6,7 @@
 
 import kotlinx.serialization.modules.*
 import kotlinx.serialization.protobuf.*
+import kotlin.native.concurrent.*
 
 @Serializable
 open class PolyBase(@ProtoNumber(1) val id: Int) {
@@ -45,6 +46,7 @@
 @Serializable
 data class PolyBox(@Polymorphic val boxed: SimpleAbstract)
 
+@SharedImmutable
 val SimplePolymorphicModule = SerializersModule {
     polymorphic(SimpleAbstract::class) {
         subclass(SimpleIntInheritor.serializer())
diff --git a/gradle.properties b/gradle.properties
index 9e7c8e3..47ed36c 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -5,7 +5,7 @@
 group=org.jetbrains.kotlinx
 version=1.1.0-SNAPSHOT
 
-kotlin.version=1.4.30-M1
+kotlin.version=1.4.30-RC
 
 # This version take precedence if 'bootstrap' property passed to project
 kotlin.version.snapshot=1.4.255-SNAPSHOT
diff --git a/gradle/configure-source-sets.gradle b/gradle/configure-source-sets.gradle
index 827ff26..fdb3f33 100644
--- a/gradle/configure-source-sets.gradle
+++ b/gradle/configure-source-sets.gradle
@@ -12,6 +12,7 @@
             }
         }
     }
+
     js {
         nodejs {}
         configure([compilations.main, compilations.test]) {
@@ -87,4 +88,19 @@
     sourceSets.matching({ it.name.contains("Main") }).all { srcSet ->
         project.ext.set("kotlin.mpp.freeCompilerArgsForSourceSet.${srcSet.name}", "-Xexplicit-api=warning")
     }
+
+    def targetsWithoutTestRunners = ["linuxArm32Hfp", "linuxArm64", "mingwX86"]
+    configure(targets) {
+        // Configure additional binaries to run tests in the background
+        if (["macos", "linux", "mingw"].any { name.startsWith(it) && !targetsWithoutTestRunners.contains(name) }) {
+            binaries {
+                test("background", [nativeDebugBuild]) {
+                    freeCompilerArgs += ["-trw"]
+                }
+            }
+            testRuns {
+                background { setExecutionSourceFrom(binaries.backgroundDebugTest) }
+            }
+        }
+    }
 }
diff --git a/gradle/native-targets.gradle b/gradle/native-targets.gradle
index fb9901a..e5a7e0b 100644
--- a/gradle/native-targets.gradle
+++ b/gradle/native-targets.gradle
@@ -88,7 +88,6 @@
 
     targets {
         if (project.ext.nativeState == NativeState.DISABLED) return
-
         if (project.ext.singleTargetMode) {
             fromPreset(project.ext.ideaPreset, 'native')
         } else {
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 62d4c05..e708b1c 100644
--- a/gradle/wrapper/gradle-wrapper.jar
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 4d9ca16..e66b0d6 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,9 @@
+#
+# Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+#
+
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index fbd7c51..4f906e0 100755
--- a/gradlew
+++ b/gradlew
@@ -130,7 +130,7 @@
 if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
     APP_HOME=`cygpath --path --mixed "$APP_HOME"`
     CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
-    
+
     JAVACMD=`cygpath --unix "$JAVACMD"`
 
     # We build the pattern for arguments to be converted via cygpath
diff --git a/gradlew.bat b/gradlew.bat
index a9f778a..ac1b06f 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -40,7 +40,7 @@
 

 set JAVA_EXE=java.exe

 %JAVA_EXE% -version >NUL 2>&1

-if "%ERRORLEVEL%" == "0" goto init

+if "%ERRORLEVEL%" == "0" goto execute

 

 echo.

 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

@@ -54,7 +54,7 @@
 set JAVA_HOME=%JAVA_HOME:"=%

 set JAVA_EXE=%JAVA_HOME%/bin/java.exe

 

-if exist "%JAVA_EXE%" goto init

+if exist "%JAVA_EXE%" goto execute

 

 echo.

 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

@@ -64,21 +64,6 @@
 

 goto fail

 

-:init

-@rem Get command-line arguments, handling Windows variants

-

-if not "%OS%" == "Windows_NT" goto win9xME_args

-

-:win9xME_args

-@rem Slurp the command line arguments.

-set CMD_LINE_ARGS=

-set _SKIP=2

-

-:win9xME_args_slurp

-if "x%~1" == "x" goto execute

-

-set CMD_LINE_ARGS=%*

-

 :execute

 @rem Setup the command line

 

@@ -86,7 +71,7 @@
 

 

 @rem Execute Gradle

-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*

 

 :end

 @rem End local scope for the variables with windows NT shell