Unverified Commit 5455c4e9 authored by Matteo Bigoi's avatar Matteo Bigoi Committed by GitHub
Browse files

Fix bug in coroutine scheduling. Add ByteStream implementation and simplify...


Fix bug in coroutine scheduling. Add ByteStream implementation and simplify Python symbol visitor. (#1633)

* Fix bug in coroutine scheduling. Add ByteStream implementation and simplify Python symbol visitor.

* Bug in coroutine scheduling:

In the previous iteration, the runtime crate aws-smithy-http-server-python
was exposing the python application implementation as a struct, which
was wrapped by the codegenerated App to allow to dynamically building
the router.

This caused scheduling of coroutines (handlers prefixed with async def)
to block becuse we were passing the Python eventloop of the parent
process that was stored pre-fork().

This commit changes the runtime PyApp to become a trait, allowing us to
dynamically build the router post-fork() and with the right event loop.

This change also allowed us to remove a bunch of unnecessary Arc(s).

* Add ByteStream implementation

Implementation of a ByteStream type for Python that can be roughly used like this:

let b = await ByteStream.from_path("file.txt")
async for chunk in b:
    print(chunk)

Implement futures_core::stream::Stream for Python ByteStream wrapper.

The BytesStream implementation in python can now use a sync Mutex from
parking_lot because we are now using pyo3_asyncio::tokio::local_future_into_py()
to read a chunk, which supports !Send futures.

This allows to simply forward the implementation of Stream (poll_next()
and size_hint()) directly to our inner SDK ByteStream.

* Simplify Python symbol visitor

Inherit from Symbol visitor and override just what is needed to swap Python complex
types.

Signed-off-by: default avatarBigo <1781140+crisidev@users.noreply.github.com>
parent 7e7d5718
Loading
Loading
Loading
Loading
+18 −3
Original line number Diff line number Diff line
@@ -13,7 +13,7 @@ use aws.protocols#restJson1
service PokemonService {
    version: "2021-12-01",
    resources: [PokemonSpecies],
    operations: [GetServerStatistics, EmptyOperation, HealthCheckOperation],
    operations: [GetServerStatistics, EmptyOperation, HealthCheckOperation, StreamPokemonRadioOperation],
}

/// A Pokémon species forms the basis for at least one Pokémon.
@@ -70,6 +70,22 @@ structure GetServerStatisticsOutput {
    calls_count: Long,
}

/// Fetch the radio song from the database and stream it back as a playable audio.
@readonly
@http(uri: "/radio", method: "GET")
operation StreamPokemonRadioOperation {
    output: StreamPokemonRadioOutput,
}

@output
structure StreamPokemonRadioOutput {
    @httpPayload
    data: StreamingBlob,
}

@streaming
blob StreamingBlob

list FlavorTextEntries {
    member: FlavorText
}
@@ -127,8 +143,7 @@ structure EmptyOperationOutput { }
/// Not yet a deep check
@readonly
@http(uri: "/ping", method: "GET")
operation HealthCheckOperation {
}
operation HealthCheckOperation { }

@error("client")
@httpError(404)
+6 −10
Original line number Diff line number Diff line
@@ -12,13 +12,12 @@ import software.amazon.smithy.model.Model
import software.amazon.smithy.model.shapes.ServiceShape
import software.amazon.smithy.rust.codegen.rustlang.RustReservedWordSymbolProvider
import software.amazon.smithy.rust.codegen.server.python.smithy.customizations.DECORATORS
import software.amazon.smithy.rust.codegen.server.python.smithy.generators.PythonServerSymbolProvider
import software.amazon.smithy.rust.codegen.server.python.smithy.generators.PythonServerSymbolVisitor
import software.amazon.smithy.rust.codegen.server.python.smithy.generators.PythonStreamingShapeMetadataProvider
import software.amazon.smithy.rust.codegen.server.smithy.customizations.ServerRequiredCustomizations
import software.amazon.smithy.rust.codegen.smithy.BaseSymbolMetadataProvider
import software.amazon.smithy.rust.codegen.smithy.EventStreamSymbolProvider
import software.amazon.smithy.rust.codegen.smithy.ServerCodegenContext
import software.amazon.smithy.rust.codegen.smithy.StreamingShapeMetadataProvider
import software.amazon.smithy.rust.codegen.smithy.StreamingShapeSymbolProvider
import software.amazon.smithy.rust.codegen.smithy.SymbolVisitor
import software.amazon.smithy.rust.codegen.smithy.SymbolVisitorConfig
import software.amazon.smithy.rust.codegen.smithy.customize.CombinedCodegenDecorator
@@ -68,20 +67,17 @@ class PythonCodegenServerPlugin : SmithyBuildPlugin {
            serviceShape: ServiceShape,
            symbolVisitorConfig: SymbolVisitorConfig,
        ) =
            SymbolVisitor(model, serviceShape = serviceShape, config = symbolVisitorConfig)
            // Rename a set of symbols that do not implement `PyClass` and have been wrapped in
            // `aws_smithy_http_server_python::types`.
            PythonServerSymbolVisitor(model, serviceShape = serviceShape, config = symbolVisitorConfig)
                // Generate different types for EventStream shapes (e.g. transcribe streaming)
                .let {
                    EventStreamSymbolProvider(symbolVisitorConfig.runtimeConfig, it, model, CodegenTarget.SERVER)
                }
                // Generate [ByteStream] instead of `Blob` for streaming binary shapes (e.g. S3 GetObject)
                .let { StreamingShapeSymbolProvider(it, model) }
                // Rename a set of symbols that do not implement `PyClass` and have been wrapped in
                // `aws_smithy_http_server_python::types`.
                .let { PythonServerSymbolProvider(it, model) }
                // Add Rust attributes (like `#[derive(PartialEq)]`) to generated shapes
                .let { BaseSymbolMetadataProvider(it, model, additionalAttributes = listOf()) }
                // Streaming shapes need different derives (e.g. they cannot derive Eq)
                .let { StreamingShapeMetadataProvider(it, model) }
                .let { PythonStreamingShapeMetadataProvider(it, model) }
                // Rename shapes that clash with Rust reserved words & and other SDK specific features e.g. `send()` cannot
                // be the name of an operation input
                .let { RustReservedWordSymbolProvider(it, model) }
+1 −0
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ object PythonServerCargoDependency {
    val TowerHttp: CargoDependency = CargoDependency("tower-http", CratesIo("0.3"), features = setOf("trace"))
    val Hyper: CargoDependency = CargoDependency("hyper", CratesIo("0.14.12"), features = setOf("server", "http1", "http2", "tcp", "stream"))
    val NumCpus: CargoDependency = CargoDependency("num_cpus", CratesIo("1.13"))
    val ParkingLot: CargoDependency = CargoDependency("parking_lot", CratesIo("0.12"))

    fun SmithyHttpServer(runtimeConfig: RuntimeConfig) = runtimeConfig.runtimeCrate("http-server")
    fun SmithyHttpServerPython(runtimeConfig: RuntimeConfig) = runtimeConfig.runtimeCrate("http-server-python")
+0 −4
Original line number Diff line number Diff line
@@ -27,7 +27,6 @@ import software.amazon.smithy.rust.codegen.smithy.generators.BuilderGenerator
import software.amazon.smithy.rust.codegen.smithy.generators.CodegenTarget
import software.amazon.smithy.rust.codegen.smithy.generators.implBlock
import software.amazon.smithy.rust.codegen.util.getTrait
import software.amazon.smithy.rust.codegen.util.hasStreamingMember

/**
 * Entrypoint for Python server-side code generation. This class will walk the in-memory model and
@@ -83,9 +82,6 @@ class PythonServerCodegenVisitor(
     * This function _does not_ generate any serializers.
     */
    override fun structureShape(shape: StructureShape) {
        if (shape.hasStreamingMember(model)) {
            throw CodegenException("Streaming members are not supported in Python yet")
        }
        logger.info("[python-server-codegen] Generating a structure $shape")
        rustCrate.useShapeWriter(shape) { writer ->
            // Use Python specific structure generator that adds the #[pyclass] attribute
+3 −0
Original line number Diff line number Diff line
@@ -21,6 +21,9 @@ object PythonServerRuntimeType {
    fun Blob(runtimeConfig: RuntimeConfig) =
        RuntimeType("Blob", PythonServerCargoDependency.SmithyHttpServerPython(runtimeConfig), "${runtimeConfig.crateSrcPrefix}_http_server_python::types")

    fun ByteStream(runtimeConfig: RuntimeConfig) =
        RuntimeType("ByteStream", PythonServerCargoDependency.SmithyHttpServerPython(runtimeConfig), "${runtimeConfig.crateSrcPrefix}_http_server_python::types")

    fun DateTime(runtimeConfig: RuntimeConfig) =
        RuntimeType("DateTime", PythonServerCargoDependency.SmithyHttpServerPython(runtimeConfig), "${runtimeConfig.crateSrcPrefix}_http_server_python::types")

Loading