Skip to content

새로운 메모리 관리자로 마이그레이션

Kotlin 1.9.20부터 레거시 메모리 관리자 지원이 완전히 제거되었습니다. Kotlin 1.7.20부터 기본적으로 활성화된 현재 메모리 모델로 프로젝트를 마이그레이션하세요.

이 가이드는 새로운 Kotlin/Native 메모리 관리자를 레거시 관리자와 비교하고 프로젝트 마이그레이션 방법을 설명합니다.

새로운 메모리 관리자에서 가장 눈에 띄는 변경 사항은 객체 공유에 대한 제한이 해제되었다는 것입니다. 객체를 스레드 간에 공유하기 위해 더 이상 고정(freeze)할 필요가 없으며, 구체적으로 다음과 같습니다:

  • 최상위 프로퍼티는 @SharedImmutable을 사용하지 않고도 어떤 스레드에서든 접근하고 수정할 수 있습니다.
  • 인터롭(interop)을 통해 전달되는 객체는 고정하지 않고도 어떤 스레드에서든 접근하고 수정할 수 있습니다.
  • Worker.executeAfter는 더 이상 작업이 고정될 것을 요구하지 않습니다.
  • Worker.execute는 더 이상 생산자가 격리된 객체 서브그래프를 반환할 것을 요구하지 않습니다.
  • AtomicReferenceFreezableAtomicReference를 포함하는 참조 주기는 메모리 누수를 유발하지 않습니다.

손쉬운 객체 공유 외에도 새로운 메모리 관리자는 다른 주요 변경 사항도 제공합니다:

  • 전역 프로퍼티는 해당 파일에 처음 접근할 때 지연 초기화됩니다. 이전에는 전역 프로퍼티가 프로그램 시작 시 초기화되었습니다. 이 문제를 해결하기 위해, 프로그램 시작 시 초기화되어야 하는 프로퍼티를 @EagerInitialization 애노테이션으로 마크할 수 있습니다. 사용하기 전에 문서를 확인하세요.
  • by lazy {} 프로퍼티는 스레드 안전 모드를 지원하며 무한 재귀를 처리하지 않습니다.
  • Worker.executeAfteroperation에서 탈출하는 예외는 다른 런타임 부분과 동일하게 처리됩니다. 사용자 정의 처리되지 않은 예외 훅을 실행하려고 시도하거나, 훅을 찾을 수 없거나 훅 자체가 예외를 발생시킨 경우 프로그램을 종료합니다.
  • 고정(Freezing)은 더 이상 사용되지 않으며 항상 비활성화됩니다.

레거시 메모리 관리자에서 프로젝트를 마이그레이션하려면 다음 지침을 따르세요:

Kotlin 업데이트

새로운 Kotlin/Native 메모리 관리자는 Kotlin 1.7.20부터 기본적으로 활성화되었습니다. Kotlin 버전을 확인하고 필요한 경우 최신 버전으로 업데이트하세요.

의존성 업데이트

kotlinx.coroutines

버전 1.6.0 이상으로 업데이트하세요. native-mt 접미사가 있는 버전은 사용하지 마세요.

새로운 메모리 관리자와 관련된 몇 가지 특이 사항도 염두에 두어야 합니다:

  • 고정이 필요하지 않으므로 모든 공통 프리미티브(채널, 플로우, 코루틴)는 Worker 경계를 통해 작동합니다.
  • Dispatchers.Default는 Linux 및 Windows에서는 Worker 풀로 지원되고 Apple 대상에서는 전역 큐로 지원됩니다.
  • Worker로 지원되는 코루틴 디스패처를 생성하려면 newSingleThreadContext를 사용하세요.
  • N개의 Worker 풀로 지원되는 코루틴 디스패처를 생성하려면 newFixedThreadPoolContext를 사용하세요.
  • Dispatchers.Main은 Darwin에서는 메인 큐로 지원되고 다른 플랫폼에서는 독립 Worker로 지원됩니다.
Ktor
버전 2.0 이상으로 업데이트하세요.
다른 의존성

대부분의 라이브러리는 변경 없이 작동해야 하지만, 예외가 있을 수 있습니다.

의존성을 최신 버전으로 업데이트하고, 레거시 및 새로운 메모리 관리자를 위한 라이브러리 버전 간에 차이가 없는지 확인하세요.

코드 업데이트

새로운 메모리 관리자를 지원하려면 영향받는 API 사용을 제거하세요:

이전 API수행할 작업
@SharedImmutable모든 사용을 제거할 수 있지만, 새로운 메모리 관리자에서 이 API를 사용해도 경고는 없습니다.
FreezableAtomicReference 클래스대신 AtomicReference를 사용하세요.
FreezingException 클래스모든 사용을 제거하세요.
InvalidMutabilityException 클래스모든 사용을 제거하세요.
IncorrectDereferenceException 클래스모든 사용을 제거하세요.
freeze() 함수모든 사용을 제거하세요.
isFrozen 프로퍼티모든 //: # (title: Custom client plugins)

Starting with v2.2.0, Ktor provides a new API for creating custom client plugins. In general, this API doesn't require an understanding of internal Ktor concepts, such as pipelines, phases, and so on. Instead, you have access to different stages of handling requests and responses using a set of handlers, such as onRequest, onResponse, and so on.

Create and install your first plugin

In this section, we'll demonstrate how to create and install your first plugin that adds a custom header to each request:

  1. To create a plugin, call the createClientPlugin function and pass a plugin name as an argument:

    kotlin
    package com.example.plugins
    
    import io.ktor.client.plugins.api.*
    
    val CustomHeaderPlugin = createClientPlugin("CustomHeaderPlugin") {
        // Configure the plugin ...
    }

    This function returns the ClientPlugin instance that will be used to install the plugin.

  2. To append a custom header to each request, you can use the onRequest handler, which provides access to request parameters:

    kotlin
  3. To install the plugin, pass the created ClientPlugin instance to the install function inside the client's configuration block:

    kotlin
    import com.example.plugins.*
    
    val client = HttpClient(CIO) {
        install(CustomHeaderPlugin)
    }

You can find the full example here: CustomHeader.kt. In the following sections, we'll look at how to provide a plugin configuration and handle requests and responses.

Provide plugin configuration

The previous section demonstrates how to create a plugin that appends a predefined custom header to each response. Let's make this plugin more useful and provide a configuration for passing any custom header name and value:

  1. First, you need to define a configuration class:

    kotlin
  2. To use this configuration in a plugin, pass a configuration class reference to createApplicationPlugin:

    kotlin

    Given that plugin configuration fields are mutable, saving them in local variables is recommended.

  3. Finally, you can install and configure the plugin as follows:

    kotlin

You can find the full example here: CustomHeaderConfigurable.kt.

Handle requests and responses

Custom plugins provide access to different stages of handling requests and responses using a set of dedicated handlers, for example:

  • onRequest and onResponse allow you to handle requests and responses, respectively.
  • transformRequestBody and transformResponseBody can be used to apply necessary transformations to request and response bodies.

There is also the on(...) handler that allows you to invoke specific hooks that might be useful to handle other stages of a call. The tables below list all handlers in the order they are executed:

Handler Description
Handler Description
on(SetupRequest) The SetupRequest hook is executed first in request processing.
onRequest

This handler is executed for each HTTP request and allows you to modify it.

Example:

transformRequestBody

Allows you to transform a request body. In this handler, you need to serialize the body into OutgoingContent (for example, TextContent, ByteArrayContent, or FormDataContent) or return null if your transformation is not applicable.

Example:

on(Send)

The Send hook provides the ability to inspect a response and initiate additional requests if needed. This might be useful for handling redirects, retrying requests, authentication, and so on.

Example:

on(SendingRequest)

The SendingRequest hook is executed for every request, even if it's not initiated by a user. For example, if a request results in a redirect, the onRequest handler will be executed only for the original request, while on(SendingRequest) will be executed for both original and redirected requests. Similarly, if you used on(Send) to initiate an additional request, handlers will be ordered as follows:

Console
--> onRequest
--> on(Send)
--> on(SendingRequest)
<-- onResponse
--> on(SendingRequest)
<-- onResponse

Examples: ,

onResponse

This handler is executed for each incoming HTTP response and allows you to inspect it in various ways: log a response, save cookies, and so on.

Examples: ,

transformResponseBody

Allows you to transform a response body. This handler is invoked for each HttpResponse.body call. You need to deserialize the body into an instance of requestedType or return null if your transformation is not applicable.

Example:

onClose Allows you to clean resources allocated by this plugin. This handler is called when the client is closed.

Share call state

Custom plugins allow you to share any value related to a call so that you can access this value inside any handler processing this call. This value is stored as an attribute with a unique key in the call.attributes collection. The example below demonstrates how to use attributes to calculate the time between sending a request and receiving a response:

kotlin

You can find the full example here: ResponseTime.kt.

Access client configuration

You can access your client configuration using the client property, which returns the HttpClient instance. The example below shows how to get the proxy address used by the client:

kotlin
import io.ktor.client.plugins.api.*

val SimplePlugin = createClientPlugin("SimplePlugin") {
    val proxyAddress = client.engineConfig.proxy?.address()
    println("Proxy address: $proxyAddress")
}

Examples

The code samples below demonstrate several examples of custom plugins. You can find the resulting project here: client-custom-plugin.

Custom header

Shows how to create a plugin that adds a custom header to each request:

kotlin

Logging headers

Demonstrates how to create a plugin that logs request and response headers:

kotlin

Response time

Shows how to create a plugin that measures the time between sending a request and receiving a response:

kotlin

Data transformation

Shows how to transform request and response bodies using the transformRequestBody and transformResponseBody hooks:

kotlin
kotlin
kotlin

You can find the full example here: client-custom-plugin-data-transformation.

Authentication

A sample Ktor project showing how to use the on(Send) hook to add a bearer token to the Authorization header if an unauthorized response is received from the server:

kotlin
kotlin

You can find the full example here: client-custom-plugin-auth.사용을 제거할 수 있습니다. 고정이 사용 중단되었으므로, 이 프로퍼티는 항상 false를 반환합니다. | | ensureNeverFrozen() 함수 | 모든 사용을 제거하세요. | | atomicLazy() 함수 | 대신 lazy()를 사용하세요. | | MutableData 클래스 | 대신 일반 컬렉션을 사용하세요. | | WorkerBoundReference<out T : Any> 클래스 | T를 직접 사용하세요. | | DetachedObjectGraph<T> 클래스 | T를 직접 사용하세요. C 인터롭(interop)을 통해 값을 전달하려면 StableRef 클래스를 사용하세요. |

다음 단계