Sharing the mutable binary data (#2311)
This commit is contained in:
@@ -26,9 +26,9 @@ typealias StableObjPtr = StableRef<*>
|
|||||||
*
|
*
|
||||||
* Any [StableRef] should be manually [disposed][dispose]
|
* Any [StableRef] should be manually [disposed][dispose]
|
||||||
*/
|
*/
|
||||||
data class StableRef<out T : Any> @PublishedApi internal constructor(
|
@Suppress("NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS")
|
||||||
private val stablePtr: COpaquePointer,
|
public inline class StableRef<out T : Any> @PublishedApi internal constructor(
|
||||||
private val ref: T // Note: storing the reference itself to avoid unchecked casts.
|
private val stablePtr: COpaquePointer
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -36,7 +36,7 @@ data class StableRef<out T : Any> @PublishedApi internal constructor(
|
|||||||
/**
|
/**
|
||||||
* Creates a handle for given object.
|
* Creates a handle for given object.
|
||||||
*/
|
*/
|
||||||
fun <T : Any> create(any: T) = StableRef<T>(createStablePointer(any), any)
|
fun <T : Any> create(any: T) = StableRef<T>(createStablePointer(any))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates [StableRef] from given raw value.
|
* Creates [StableRef] from given raw value.
|
||||||
@@ -46,7 +46,6 @@ data class StableRef<out T : Any> @PublishedApi internal constructor(
|
|||||||
@Deprecated("Use CPointer<*>.asStableRef<T>() instead", ReplaceWith("ptr.asStableRef<T>()"))
|
@Deprecated("Use CPointer<*>.asStableRef<T>() instead", ReplaceWith("ptr.asStableRef<T>()"))
|
||||||
fun fromValue(value: COpaquePointer) = value.asStableRef<Any>()
|
fun fromValue(value: COpaquePointer) = value.asStableRef<Any>()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated("Use .asCPointer() instead", ReplaceWith("this.asCPointer()"))
|
@Deprecated("Use .asCPointer() instead", ReplaceWith("this.asCPointer()"))
|
||||||
val value: COpaquePointer get() = this.asCPointer()
|
val value: COpaquePointer get() = this.asCPointer()
|
||||||
|
|
||||||
@@ -66,12 +65,13 @@ data class StableRef<out T : Any> @PublishedApi internal constructor(
|
|||||||
/**
|
/**
|
||||||
* Returns the object this handle was [created][StableRef.create] for.
|
* Returns the object this handle was [created][StableRef.create] for.
|
||||||
*/
|
*/
|
||||||
fun get() = this.ref
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun get() = derefStablePointer(this.stablePtr) as T
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts to [StableRef] this opaque pointer produced by [StableRef.asCPointer].
|
* Converts to [StableRef] this opaque pointer produced by [StableRef.asCPointer].
|
||||||
*/
|
*/
|
||||||
inline fun <reified T : Any> CPointer<*>.asStableRef() =
|
inline fun <reified T : Any> CPointer<*>.asStableRef(): StableRef<T> = StableRef<T>(this)
|
||||||
StableRef<T>(this, derefStablePointer(this) as T)
|
.also { it.get() as T }
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
depends = CFNetwork CoreFoundation Foundation Security darwin posix
|
||||||
|
language = Objective-C
|
||||||
|
package = platform.Contacts
|
||||||
|
headers = Contacts/Contacts.h
|
||||||
|
|
||||||
|
headerFilter = Contacts/**
|
||||||
|
|
||||||
|
compilerOpts = -framework Contacts
|
||||||
|
linkerOpts = -framework Contacts
|
||||||
@@ -414,14 +414,14 @@ void KRefSharedHolder::initRefOwner() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void KRefSharedHolder::verifyRefOwner() const {
|
void KRefSharedHolder::verifyRefOwner() const {
|
||||||
// Note: checking for 'permanentOrFrozen()' and retrieving 'type_info()'
|
// Note: checking for 'shareable()' and retrieving 'type_info()'
|
||||||
// are supposed to be correct even for unowned object.
|
// are supposed to be correct even for unowned object.
|
||||||
if (owner_ != memoryState) {
|
if (owner_ != memoryState) {
|
||||||
// Initialized runtime is required to throw the exception below
|
// Initialized runtime is required to throw the exception below
|
||||||
// or to provide proper execution context for shared objects:
|
// or to provide proper execution context for shared objects:
|
||||||
if (memoryState == nullptr) Kotlin_initRuntimeIfNeeded();
|
if (memoryState == nullptr) Kotlin_initRuntimeIfNeeded();
|
||||||
|
|
||||||
if (!obj_->container()->permanentOrFrozen()) {
|
if (!obj_->container()->shareable()) {
|
||||||
// TODO: add some info about the owner.
|
// TODO: add some info about the owner.
|
||||||
ThrowIllegalObjectSharingException(obj_->type_info(), obj_);
|
ThrowIllegalObjectSharingException(obj_->type_info(), obj_);
|
||||||
}
|
}
|
||||||
@@ -648,7 +648,7 @@ void MarkGray(ContainerHeader* start) {
|
|||||||
auto childContainer = ref->container();
|
auto childContainer = ref->container();
|
||||||
RuntimeAssert(!isArena(childContainer), "A reference to local object is encountered");
|
RuntimeAssert(!isArena(childContainer), "A reference to local object is encountered");
|
||||||
|
|
||||||
if (!childContainer->permanentOrFrozen()) {
|
if (!childContainer->shareable()) {
|
||||||
childContainer->decRefCount<false>();
|
childContainer->decRefCount<false>();
|
||||||
toVisit.push_front(childContainer);
|
toVisit.push_front(childContainer);
|
||||||
}
|
}
|
||||||
@@ -675,7 +675,7 @@ void ScanBlack(ContainerHeader* start) {
|
|||||||
traverseContainerReferredObjects(container, [&toVisit](ObjHeader* ref) {
|
traverseContainerReferredObjects(container, [&toVisit](ObjHeader* ref) {
|
||||||
auto childContainer = ref->container();
|
auto childContainer = ref->container();
|
||||||
RuntimeAssert(!isArena(childContainer), "A reference to local object is encountered");
|
RuntimeAssert(!isArena(childContainer), "A reference to local object is encountered");
|
||||||
if (!childContainer->permanentOrFrozen()) {
|
if (!childContainer->shareable()) {
|
||||||
childContainer->incRefCount<false>();
|
childContainer->incRefCount<false>();
|
||||||
if (useColor) {
|
if (useColor) {
|
||||||
if (childContainer->color() != CONTAINER_TAG_GC_BLACK)
|
if (childContainer->color() != CONTAINER_TAG_GC_BLACK)
|
||||||
@@ -744,7 +744,7 @@ void Scan(ContainerHeader* container) {
|
|||||||
traverseContainerReferredObjects(container, [](ObjHeader* ref) {
|
traverseContainerReferredObjects(container, [](ObjHeader* ref) {
|
||||||
auto childContainer = ref->container();
|
auto childContainer = ref->container();
|
||||||
RuntimeAssert(!isArena(childContainer), "A reference to local object is encountered");
|
RuntimeAssert(!isArena(childContainer), "A reference to local object is encountered");
|
||||||
if (!childContainer->permanentOrFrozen()) {
|
if (!childContainer->shareable()) {
|
||||||
Scan(childContainer);
|
Scan(childContainer);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -764,7 +764,7 @@ void CollectWhite(MemoryState* state, ContainerHeader* start) {
|
|||||||
if (ref == nullptr) return;
|
if (ref == nullptr) return;
|
||||||
auto childContainer = ref->container();
|
auto childContainer = ref->container();
|
||||||
RuntimeAssert(!isArena(childContainer), "A reference to local object is encountered");
|
RuntimeAssert(!isArena(childContainer), "A reference to local object is encountered");
|
||||||
if (childContainer->permanentOrFrozen()) {
|
if (childContainer->shareable()) {
|
||||||
UpdateRef(location, nullptr);
|
UpdateRef(location, nullptr);
|
||||||
} else {
|
} else {
|
||||||
toVisit.push_front(childContainer);
|
toVisit.push_front(childContainer);
|
||||||
@@ -787,6 +787,7 @@ inline void AddRef(ContainerHeader* header) {
|
|||||||
IncrementRC<false>(header);
|
IncrementRC<false>(header);
|
||||||
break;
|
break;
|
||||||
case CONTAINER_TAG_FROZEN:
|
case CONTAINER_TAG_FROZEN:
|
||||||
|
case CONTAINER_TAG_ATOMIC:
|
||||||
IncrementRC<true>(header);
|
IncrementRC<true>(header);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -806,6 +807,7 @@ inline void Release(ContainerHeader* header, bool useCycleCollector) {
|
|||||||
DecrementRC<false>(header, useCycleCollector);
|
DecrementRC<false>(header, useCycleCollector);
|
||||||
break;
|
break;
|
||||||
case CONTAINER_TAG_FROZEN:
|
case CONTAINER_TAG_FROZEN:
|
||||||
|
case CONTAINER_TAG_ATOMIC:
|
||||||
DecrementRC<true>(header, useCycleCollector);
|
DecrementRC<true>(header, useCycleCollector);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -1537,7 +1539,7 @@ bool hasExternalRefs(ContainerHeader* container, ContainerHeaderSet* visited) {
|
|||||||
bool result = container->refCount() != 0;
|
bool result = container->refCount() != 0;
|
||||||
traverseContainerReferredObjects(container, [&result, visited](ObjHeader* ref) {
|
traverseContainerReferredObjects(container, [&result, visited](ObjHeader* ref) {
|
||||||
auto child = ref->container();
|
auto child = ref->container();
|
||||||
if (!child->permanentOrFrozen() && (visited->find(child) == visited->end())) {
|
if (!child->shareable() && (visited->find(child) == visited->end())) {
|
||||||
result |= hasExternalRefs(child, visited);
|
result |= hasExternalRefs(child, visited);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1560,7 +1562,7 @@ bool ClearSubgraphReferences(ObjHeader* root, bool checked) {
|
|||||||
if (!checked) {
|
if (!checked) {
|
||||||
hasExternalRefs(container, &visited);
|
hasExternalRefs(container, &visited);
|
||||||
} else {
|
} else {
|
||||||
if (!container->permanentOrFrozen()) {
|
if (!container->shareable()) {
|
||||||
container->decRefCount<false>();
|
container->decRefCount<false>();
|
||||||
MarkGray<false>(container);
|
MarkGray<false>(container);
|
||||||
auto bad = hasExternalRefs(container, &visited);
|
auto bad = hasExternalRefs(container, &visited);
|
||||||
@@ -1604,7 +1606,7 @@ void depthFirstTraversal(ContainerHeader* container, bool* hasCycles,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ContainerHeader* objContainer = obj->container();
|
ContainerHeader* objContainer = obj->container();
|
||||||
if (!objContainer->permanentOrFrozen()) {
|
if (!objContainer->shareable()) {
|
||||||
// Marked GREY, there's cycle.
|
// Marked GREY, there's cycle.
|
||||||
if (objContainer->seen()) *hasCycles = true;
|
if (objContainer->seen()) *hasCycles = true;
|
||||||
|
|
||||||
@@ -1647,7 +1649,7 @@ void freezeAcyclic(ContainerHeader* rootContainer) {
|
|||||||
current->freeze();
|
current->freeze();
|
||||||
traverseContainerReferredObjects(current, [current, &queue](ObjHeader* obj) {
|
traverseContainerReferredObjects(current, [current, &queue](ObjHeader* obj) {
|
||||||
ContainerHeader* objContainer = obj->container();
|
ContainerHeader* objContainer = obj->container();
|
||||||
if (!objContainer->permanentOrFrozen()) {
|
if (!objContainer->shareable()) {
|
||||||
if (objContainer->marked())
|
if (objContainer->marked())
|
||||||
queue.push_back(objContainer);
|
queue.push_back(objContainer);
|
||||||
}
|
}
|
||||||
@@ -1666,7 +1668,7 @@ void freezeCyclic(ContainerHeader* rootContainer, const KStdVector<ContainerHead
|
|||||||
reversedEdges.emplace(current, KStdVector<ContainerHeader*>(0));
|
reversedEdges.emplace(current, KStdVector<ContainerHeader*>(0));
|
||||||
traverseContainerReferredObjects(current, [current, &queue, &reversedEdges](ObjHeader* obj) {
|
traverseContainerReferredObjects(current, [current, &queue, &reversedEdges](ObjHeader* obj) {
|
||||||
ContainerHeader* objContainer = obj->container();
|
ContainerHeader* objContainer = obj->container();
|
||||||
if (!objContainer->permanentOrFrozen()) {
|
if (!objContainer->shareable()) {
|
||||||
if (objContainer->marked())
|
if (objContainer->marked())
|
||||||
queue.push_back(objContainer);
|
queue.push_back(objContainer);
|
||||||
reversedEdges.emplace(objContainer, KStdVector<ContainerHeader*>(0)).first->second.push_back(current);
|
reversedEdges.emplace(objContainer, KStdVector<ContainerHeader*>(0)).first->second.push_back(current);
|
||||||
@@ -1698,7 +1700,7 @@ void freezeCyclic(ContainerHeader* rootContainer, const KStdVector<ContainerHead
|
|||||||
for (auto* container : component) {
|
for (auto* container : component) {
|
||||||
totalCount += container->refCount();
|
totalCount += container->refCount();
|
||||||
traverseContainerReferredObjects(container, [&internalRefsCount](ObjHeader* obj) {
|
traverseContainerReferredObjects(container, [&internalRefsCount](ObjHeader* obj) {
|
||||||
if (!obj->container()->permanentOrFrozen())
|
if (!obj->container()->shareable())
|
||||||
++internalRefsCount;
|
++internalRefsCount;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1746,7 +1748,7 @@ void FreezeSubgraph(ObjHeader* root) {
|
|||||||
// First check that passed object graph has no cycles.
|
// First check that passed object graph has no cycles.
|
||||||
// If there are cycles - run graph condensation on cyclic graphs using Kosoraju-Sharir.
|
// If there are cycles - run graph condensation on cyclic graphs using Kosoraju-Sharir.
|
||||||
ContainerHeader* rootContainer = root->container();
|
ContainerHeader* rootContainer = root->container();
|
||||||
if (rootContainer->permanentOrFrozen()) return;
|
if (rootContainer->shareable()) return;
|
||||||
|
|
||||||
// Do DFS cycle detection.
|
// Do DFS cycle detection.
|
||||||
bool hasCycles = false;
|
bool hasCycles = false;
|
||||||
@@ -1872,4 +1874,11 @@ KBoolean Konan_ensureAcyclicAndSet(ObjHeader* where, KInt index, ObjHeader* what
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Kotlin_Any_share(ObjHeader* obj) {
|
||||||
|
auto container = obj->container();
|
||||||
|
if (container->shareable()) return;
|
||||||
|
RuntimeCheck(container->objectCount() == 1, "Must be a single object container");
|
||||||
|
container->makeShareable();
|
||||||
|
}
|
||||||
|
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
|||||||
@@ -27,14 +27,16 @@ typedef enum {
|
|||||||
CONTAINER_TAG_NORMAL = 0,
|
CONTAINER_TAG_NORMAL = 0,
|
||||||
// Container is frozen, could only refer to other frozen objects.
|
// Container is frozen, could only refer to other frozen objects.
|
||||||
// Refcounter update is atomics.
|
// Refcounter update is atomics.
|
||||||
CONTAINER_TAG_FROZEN = 1,
|
CONTAINER_TAG_FROZEN = 1 | 1, // shareable
|
||||||
|
// Stack container, no need to free, children cleanup still shall be there.
|
||||||
|
CONTAINER_TAG_STACK = 2,
|
||||||
// Those container tags shall not be refcounted.
|
// Those container tags shall not be refcounted.
|
||||||
// Permanent container, cannot refer to non-permanent containers, so no need to cleanup those.
|
// Permanent container, cannot refer to non-permanent containers, so no need to cleanup those.
|
||||||
CONTAINER_TAG_PERMANENT = 2,
|
CONTAINER_TAG_PERMANENT = 3 | 1, // shareable
|
||||||
// Stack container, no need to free, children cleanup still shall be there.
|
// Atomic container, reference counter is atomically updated.
|
||||||
CONTAINER_TAG_STACK = 3,
|
CONTAINER_TAG_ATOMIC = 5 | 1, // shareable
|
||||||
// Shift to get actual counter.
|
// Shift to get actual counter.
|
||||||
CONTAINER_TAG_SHIFT = 2,
|
CONTAINER_TAG_SHIFT = 3,
|
||||||
// Actual value to increment/decrement container by. Tag is in lower bits.
|
// Actual value to increment/decrement container by. Tag is in lower bits.
|
||||||
CONTAINER_TAG_INCREMENT = 1 << CONTAINER_TAG_SHIFT,
|
CONTAINER_TAG_INCREMENT = 1 << CONTAINER_TAG_SHIFT,
|
||||||
// Mask for container type.
|
// Mask for container type.
|
||||||
@@ -83,10 +85,18 @@ struct ContainerHeader {
|
|||||||
refCount_ = (refCount_ & ~CONTAINER_TAG_MASK) | CONTAINER_TAG_FROZEN;
|
refCount_ = (refCount_ & ~CONTAINER_TAG_MASK) | CONTAINER_TAG_FROZEN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void makeShareable() {
|
||||||
|
refCount_ = (refCount_ & ~CONTAINER_TAG_MASK) | CONTAINER_TAG_ATOMIC;
|
||||||
|
}
|
||||||
|
|
||||||
inline bool permanentOrFrozen() const {
|
inline bool permanentOrFrozen() const {
|
||||||
return tag() == CONTAINER_TAG_PERMANENT || tag() == CONTAINER_TAG_FROZEN;
|
return tag() == CONTAINER_TAG_PERMANENT || tag() == CONTAINER_TAG_FROZEN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool shareable() const {
|
||||||
|
return (tag() & 1) != 0; // CONTAINER_TAG_PERMANENT || CONTAINER_TAG_FROZEN || CONTAINER_TAG_ATOMIC
|
||||||
|
}
|
||||||
|
|
||||||
inline bool stack() const {
|
inline bool stack() const {
|
||||||
return (refCount_ & CONTAINER_TAG_MASK) == CONTAINER_TAG_STACK;
|
return (refCount_ & CONTAINER_TAG_MASK) == CONTAINER_TAG_STACK;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,4 +78,8 @@ const void* Kotlin_Any_getTypeInfo(KConstRef obj) {
|
|||||||
return obj->type_info();
|
return obj->type_info();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Kotlin_CPointer_CopyMemory(KNativePtr to, KNativePtr from, KInt count) {
|
||||||
|
memcpy(to, from, count);
|
||||||
|
}
|
||||||
|
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package kotlin.native.concurrent
|
||||||
|
|
||||||
|
import kotlin.native.internal.*
|
||||||
|
import kotlinx.cinterop.*
|
||||||
|
|
||||||
|
public class Continuation0(block: () -> Unit,
|
||||||
|
private val invoker: CPointer<CFunction<(COpaquePointer?) -> Unit>>,
|
||||||
|
private val singleShot: Boolean = false): Function0<Unit> {
|
||||||
|
|
||||||
|
private val stable = StableRef.create(block)
|
||||||
|
|
||||||
|
init {
|
||||||
|
freeze()
|
||||||
|
}
|
||||||
|
|
||||||
|
public override operator fun invoke() {
|
||||||
|
invoker(stable.asCPointer())
|
||||||
|
if (singleShot) {
|
||||||
|
stable.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun dispose() {
|
||||||
|
assert(!singleShot)
|
||||||
|
stable.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Continuation1<T1>(
|
||||||
|
block: (p1: T1) -> Unit,
|
||||||
|
private val invoker: CPointer<CFunction<(COpaquePointer?) -> Unit>>,
|
||||||
|
private val singleShot: Boolean = false) : Function1<T1, Unit> {
|
||||||
|
|
||||||
|
private val stable = StableRef.create(block)
|
||||||
|
|
||||||
|
init {
|
||||||
|
freeze()
|
||||||
|
}
|
||||||
|
|
||||||
|
public override operator fun invoke(p1: T1) {
|
||||||
|
val args = StableRef.create(Pair(stable, p1))
|
||||||
|
try {
|
||||||
|
invoker(args.asCPointer())
|
||||||
|
} finally {
|
||||||
|
args.dispose()
|
||||||
|
}
|
||||||
|
if (singleShot) {
|
||||||
|
stable.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun dispose() {
|
||||||
|
assert(!singleShot)
|
||||||
|
stable.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Continuation2<T1, T2>(
|
||||||
|
block: (p1: T1, p2: T2) -> Unit,
|
||||||
|
private val invoker: CPointer<CFunction<(COpaquePointer?) -> Unit>>,
|
||||||
|
private val singleShot: Boolean = false) : Function2<T1, T2, Unit> {
|
||||||
|
|
||||||
|
private val stable = StableRef.create(block)
|
||||||
|
|
||||||
|
init {
|
||||||
|
freeze()
|
||||||
|
}
|
||||||
|
|
||||||
|
public override operator fun invoke(p1: T1, p2: T2) {
|
||||||
|
val args = StableRef.create(Triple(stable, p1, p2))
|
||||||
|
try {
|
||||||
|
invoker(args.asCPointer())
|
||||||
|
} finally {
|
||||||
|
args.dispose()
|
||||||
|
}
|
||||||
|
if (singleShot) {
|
||||||
|
stable.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dispose() {
|
||||||
|
assert(!singleShot)
|
||||||
|
stable.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun COpaquePointer.callContinuation0() {
|
||||||
|
val single = this.asStableRef<() -> Unit>()
|
||||||
|
single.get()()
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun <T1> COpaquePointer.callContinuation1() {
|
||||||
|
val pair = this.asStableRef<Pair<StableRef<(T1) -> Unit>, T1>>().get()
|
||||||
|
pair.first.get()(pair.second)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun <T1, T2> COpaquePointer.callContinuation2() {
|
||||||
|
val triple = this.asStableRef<Triple<StableRef<(T1, T2) -> Unit>, T1, T2>>().get()
|
||||||
|
triple.first.get()(triple.second, triple.third)
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package kotlin.native.concurrent
|
||||||
|
|
||||||
|
import kotlin.native.internal.*
|
||||||
|
import kotlinx.cinterop.*
|
||||||
|
|
||||||
|
@SymbolName("Kotlin_Any_share")
|
||||||
|
external private fun Any.share()
|
||||||
|
|
||||||
|
@SymbolName("Kotlin_CPointer_CopyMemory")
|
||||||
|
external private fun CopyMemory(to: COpaquePointer?, from: COpaquePointer?, count: Int)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutable concurrently accessible data buffer. Could be accessed from several workers simulteniously.
|
||||||
|
*/
|
||||||
|
@Frozen
|
||||||
|
public class MutableData constructor(capacity: Int = 16) {
|
||||||
|
init {
|
||||||
|
if (capacity <= 0) throw IllegalArgumentException()
|
||||||
|
// Instance of MutableData is shared.
|
||||||
|
share()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var buffer = ByteArray(capacity).apply { share() }
|
||||||
|
private var size_ = 0
|
||||||
|
private val lock = Lock()
|
||||||
|
|
||||||
|
private fun resizeDataLocked(newSize: Int): Int {
|
||||||
|
assert(newSize >= size)
|
||||||
|
if (newSize > buffer.size) {
|
||||||
|
val actualSize = maxOf(buffer.size * 3 / 2 + 1, newSize)
|
||||||
|
val newBuffer = ByteArray(actualSize)
|
||||||
|
buffer.copyRangeTo(newBuffer, 0, size, 0)
|
||||||
|
newBuffer.share()
|
||||||
|
buffer = newBuffer
|
||||||
|
}
|
||||||
|
val position = size
|
||||||
|
size_ = newSize
|
||||||
|
return position
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current data size, may concurrently change later on.
|
||||||
|
*/
|
||||||
|
public val size: Int
|
||||||
|
get() = size_
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the data buffer, makings its size 0.
|
||||||
|
*/
|
||||||
|
public fun reset() = locked(lock) {
|
||||||
|
size_ = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends data to the buffer.
|
||||||
|
*/
|
||||||
|
public fun append(data: MutableData) = locked(lock) {
|
||||||
|
val toCopy = data.size
|
||||||
|
val where = resizeDataLocked(size + toCopy)
|
||||||
|
data.copyInto(buffer, 0, toCopy, where)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends byte array to the buffer.
|
||||||
|
*/
|
||||||
|
public fun append(data: ByteArray, fromIndex: Int = 0, toIndex: Int = data.size): Unit = locked(lock) {
|
||||||
|
if (fromIndex > toIndex)
|
||||||
|
throw IndexOutOfBoundsException("$fromIndex is bigger than $toIndex")
|
||||||
|
if (toIndex == toIndex) return
|
||||||
|
val where = resizeDataLocked(this.size + (toIndex - fromIndex))
|
||||||
|
data.copyRangeTo(buffer, fromIndex, toIndex, where)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends C data to the buffer, if `data` is null or `count` is non-positive - return.
|
||||||
|
*/
|
||||||
|
public fun append(data: COpaquePointer?, count: Int): Unit = locked(lock) {
|
||||||
|
if (data == null || count <= 0) return
|
||||||
|
val where = resizeDataLocked(this.size + count)
|
||||||
|
buffer.usePinned {
|
||||||
|
it -> CopyMemory(it.addressOf(where), data, count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies range of mutable data to the byte array.
|
||||||
|
*/
|
||||||
|
public fun copyInto(output: ByteArray, destinationIndex: Int, startIndex: Int, endIndex: Int): Unit = locked(lock) {
|
||||||
|
buffer.copyRangeTo(output, startIndex, endIndex, destinationIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a byte from the mutable data.
|
||||||
|
*
|
||||||
|
* @Throws IndexOutOfBoundsException if index is beyond range.
|
||||||
|
*/
|
||||||
|
public operator fun get(index: Int): Byte = locked(lock) {
|
||||||
|
// index < 0 is checked below by array access.
|
||||||
|
if (index >= size)
|
||||||
|
throw IndexOutOfBoundsException("$index is not below $size")
|
||||||
|
buffer[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes provided block under lock with raw pointer to the data stored in the buffer.
|
||||||
|
* Block is executed under the spinlock, and must be short.
|
||||||
|
*/
|
||||||
|
public fun <R> withPointerLocked(block: (COpaquePointer, dataSize: Int) -> R) = locked(lock) {
|
||||||
|
buffer.usePinned {
|
||||||
|
it -> block(it.addressOf(0), size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes provided block under lock with the raw data buffer.
|
||||||
|
* Block is executed under the spinlock, and must be short.
|
||||||
|
*/
|
||||||
|
public fun <R> withBufferLocked(block: (array: ByteArray, dataSize: Int) -> R) = locked(lock) {
|
||||||
|
block(buffer, size)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,12 +7,15 @@ package sample.objc
|
|||||||
|
|
||||||
import kotlinx.cinterop.*
|
import kotlinx.cinterop.*
|
||||||
import platform.AppKit.*
|
import platform.AppKit.*
|
||||||
|
import platform.Contacts.CNContactStore
|
||||||
|
import platform.Contacts.CNEntityType
|
||||||
import platform.Foundation.*
|
import platform.Foundation.*
|
||||||
import platform.darwin.NSObject
|
import platform.darwin.NSObject
|
||||||
import platform.darwin.dispatch_async_f
|
import platform.darwin.dispatch_async_f
|
||||||
import kotlin.native.concurrent.DetachedObjectGraph
|
import platform.darwin.dispatch_get_main_queue
|
||||||
import kotlin.native.concurrent.attach
|
import platform.darwin.dispatch_sync_f
|
||||||
import kotlin.native.concurrent.freeze
|
import platform.posix.memcpy
|
||||||
|
import kotlin.native.concurrent.*
|
||||||
|
|
||||||
inline fun <reified T> executeAsync(queue: NSOperationQueue, crossinline producerConsumer: () -> Pair<T, (T) -> Unit>) {
|
inline fun <reified T> executeAsync(queue: NSOperationQueue, crossinline producerConsumer: () -> Pair<T, (T) -> Unit>) {
|
||||||
dispatch_async_f(queue.underlyingQueue, DetachedObjectGraph {
|
dispatch_async_f(queue.underlyingQueue, DetachedObjectGraph {
|
||||||
@@ -23,8 +26,51 @@ inline fun <reified T> executeAsync(queue: NSOperationQueue, crossinline produce
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun mainContinuation(singleShot: Boolean = true, noinline block: () -> Unit) = Continuation0(
|
||||||
|
block, staticCFunction { invokerArg ->
|
||||||
|
if (NSThread.isMainThread()) {
|
||||||
|
invokerArg!!.callContinuation0()
|
||||||
|
} else {
|
||||||
|
dispatch_sync_f(dispatch_get_main_queue(), invokerArg, staticCFunction { args ->
|
||||||
|
args!!.callContinuation0()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, singleShot)
|
||||||
|
|
||||||
|
inline fun <T1> mainContinuation(singleShot: Boolean = true, noinline block: (T1) -> Unit) = Continuation1(
|
||||||
|
block, staticCFunction { invokerArg ->
|
||||||
|
if (NSThread.isMainThread()) {
|
||||||
|
invokerArg!!.callContinuation1<T1>()
|
||||||
|
} else {
|
||||||
|
dispatch_sync_f(dispatch_get_main_queue(), invokerArg, staticCFunction { args ->
|
||||||
|
args!!.callContinuation1<T1>()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, singleShot)
|
||||||
|
|
||||||
|
inline fun <T1, T2> mainContinuation(singleShot: Boolean = true, noinline block: (T1, T2) -> Unit) = Continuation2(
|
||||||
|
block, staticCFunction { invokerArg ->
|
||||||
|
if (NSThread.isMainThread()) {
|
||||||
|
invokerArg!!.callContinuation2<T1, T2>()
|
||||||
|
} else {
|
||||||
|
dispatch_sync_f(dispatch_get_main_queue(), invokerArg, staticCFunction { args ->
|
||||||
|
args!!.callContinuation2<T1, T2>()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, singleShot)
|
||||||
|
|
||||||
|
|
||||||
data class QueryResult(val json: Map<String, *>?, val error: String?)
|
data class QueryResult(val json: Map<String, *>?, val error: String?)
|
||||||
|
|
||||||
|
private fun MutableData.asNSData() = this.withPointerLocked { it, size ->
|
||||||
|
val result = NSMutableData.create(length = size.convert())!!
|
||||||
|
memcpy(result.mutableBytes, it, size.convert())
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MutableData.asJSON(): Map<String, *>? =
|
||||||
|
NSJSONSerialization.JSONObjectWithData(this.asNSData(), 0, null) as? Map<String, *>
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
autoreleasepool {
|
autoreleasepool {
|
||||||
runApp()
|
runApp()
|
||||||
@@ -45,7 +91,7 @@ private fun runApp() {
|
|||||||
|
|
||||||
class Controller : NSObject() {
|
class Controller : NSObject() {
|
||||||
private var index = 1
|
private var index = 1
|
||||||
private var httpDelegate = HttpDelegate()
|
private val httpDelegate = HttpDelegate()
|
||||||
|
|
||||||
@ObjCAction
|
@ObjCAction
|
||||||
fun onClick() {
|
fun onClick() {
|
||||||
@@ -63,16 +109,28 @@ class Controller : NSObject() {
|
|||||||
NSApplication.sharedApplication().stop(this)
|
NSApplication.sharedApplication().stop(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ObjCAction
|
||||||
|
fun onRequest() {
|
||||||
|
val addressBookRef = CNContactStore()
|
||||||
|
addressBookRef.requestAccessForEntityType(CNEntityType.CNEntityTypeContacts, mainContinuation {
|
||||||
|
granted, error ->
|
||||||
|
appDelegate.contentText.string = if (granted)
|
||||||
|
"Access granted!"
|
||||||
|
else
|
||||||
|
"Access denied: $error"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
class HttpDelegate: NSObject(), NSURLSessionDataDelegateProtocol {
|
class HttpDelegate: NSObject(), NSURLSessionDataDelegateProtocol {
|
||||||
private val asyncQueue = NSOperationQueue()
|
private val asyncQueue = NSOperationQueue()
|
||||||
internal val receivedData = NSMutableData()
|
private val receivedData = MutableData()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
freeze()
|
freeze()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fetchUrl(url: String) {
|
fun fetchUrl(url: String) {
|
||||||
receivedData.setLength(0)
|
receivedData.reset()
|
||||||
val session = NSURLSession.sessionWithConfiguration(
|
val session = NSURLSession.sessionWithConfiguration(
|
||||||
NSURLSessionConfiguration.defaultSessionConfiguration(),
|
NSURLSessionConfiguration.defaultSessionConfiguration(),
|
||||||
this,
|
this,
|
||||||
@@ -83,7 +141,7 @@ class Controller : NSObject() {
|
|||||||
|
|
||||||
override fun URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData: NSData) {
|
override fun URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData: NSData) {
|
||||||
initRuntimeIfNeeded()
|
initRuntimeIfNeeded()
|
||||||
receivedData.appendData(didReceiveData)
|
receivedData.append(didReceiveData.bytes, didReceiveData.length.convert())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError: NSError?) {
|
override fun URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError: NSError?) {
|
||||||
@@ -94,10 +152,7 @@ class Controller : NSObject() {
|
|||||||
Pair(when {
|
Pair(when {
|
||||||
response == null -> QueryResult(null, didCompleteWithError?.localizedDescription)
|
response == null -> QueryResult(null, didCompleteWithError?.localizedDescription)
|
||||||
response.statusCode.toInt() != 200 -> QueryResult(null, "${response.statusCode.toInt()})")
|
response.statusCode.toInt() != 200 -> QueryResult(null, "${response.statusCode.toInt()})")
|
||||||
else -> QueryResult(
|
else -> QueryResult(receivedData.asJSON(), null)
|
||||||
NSJSONSerialization.JSONObjectWithData(receivedData, 0, null) as? Map<String, *>,
|
|
||||||
null
|
|
||||||
)
|
|
||||||
}, { result: QueryResult ->
|
}, { result: QueryResult ->
|
||||||
appDelegate.contentText.string = result.json?.toString() ?: "Error: ${result.error}"
|
appDelegate.contentText.string = result.json?.toString() ?: "Error: ${result.error}"
|
||||||
appDelegate.canClick = true
|
appDelegate.canClick = true
|
||||||
@@ -157,6 +212,14 @@ class MyAppDelegate() : NSObject(), NSApplicationDelegateProtocol {
|
|||||||
action = NSSelectorFromString("onQuit")
|
action = NSSelectorFromString("onQuit")
|
||||||
}
|
}
|
||||||
window.contentView!!.addSubview(buttonQuit)
|
window.contentView!!.addSubview(buttonQuit)
|
||||||
|
|
||||||
|
val buttonRequest = NSButton(NSMakeRect(230.0, 10.0, 100.0, 40.0)).apply {
|
||||||
|
title = "Request"
|
||||||
|
target = controller
|
||||||
|
action = NSSelectorFromString("onRequest")
|
||||||
|
}
|
||||||
|
window.contentView!!.addSubview(buttonRequest)
|
||||||
|
|
||||||
contentText = NSText(NSMakeRect(10.0, 80.0, 600.0, 350.0)).apply {
|
contentText = NSText(NSMakeRect(10.0, 80.0, 600.0, 350.0)).apply {
|
||||||
string = "Press 'Click' to start fetching"
|
string = "Press 'Click' to start fetching"
|
||||||
verticallyResizable = false
|
verticallyResizable = false
|
||||||
|
|||||||
Reference in New Issue
Block a user