Skip to content

Crash when ussl wrapped socket is running as a coroutine (no easily reproducible testcase) #3297

Closed

Description

@richarddd
  File "/Users/richard/.micropython/lib/uasyncio/core.py", line 128, in run_forever
  File "/Users/richard/.micropython/lib/uasyncio/core.py", line 100, in run_forever
  File "/Users/richard/.micropython/lib/uasyncio/__init__.py", line 34, in add_reader
TypeError: can't convert ussl to int

How to reproduce:

import usocket
from uasyncio import StreamReader, StreamWriter, coroutine, get_event_loop, sleep as sleepasyncio
import gc
import sys


class Response:

    def __init__(self, f, content_length):
        self.raw = f
        self.encoding = "utf-8"
        self._cached = None
        self.content_length = content_length

    def close(self):
        if self.raw:
            self.raw.close()
            self.raw = None
        self._cached = None

    @property
    def content(self):
        if self._cached is None:
            lines = ""
            if self.content_length < 500:
                lines = self.raw.read(self.content_length).decode("utf-8")
            else:
                while True:
                    yield
                    line = self.raw.readline()
                    if not line or line == b"\r\n" or line == b'':
                        break
                    if line:
                        lines += line.decode("utf-8")
                    # print(lines)
            self.raw.close()
            self.raw = None
            self._cached = lines
        return self._cached

    @content.setter
    def content(self, value):
        self._cached = value

    @property
    def text(self):
        return str(self.content, self.encoding)

    def json(self):
        import ujson
        data = yield from self.content
        return ujson.loads(data)


def request(method, url, data=None, json=None, headers={}, stream=None):
    try:
        proto, dummy, host, path = url.split("/", 3)
    except ValueError:
        proto, dummy, host = url.split("/", 2)
        path = ""
    if proto == "http:":
        port = 80
    elif proto == "https:":
        import ussl
        port = 443
    else:
        raise ValueError("Unsupported protocol: " + proto)

    if ":" in host:
        host, port = host.split(":", 1)
        port = int(port)

    print("host:", host)
    print("port:", port)

    gc.collect()

    try:
        ai = usocket.getaddrinfo(host, port)
        addr = ai[0][-1]
        s = usocket.socket()
        s.connect(addr)

        if proto == "https:":
            s = ussl.wrap_socket(s)

        (reader, writer) = StreamReader(s), StreamWriter(s, {})

        gc.collect()

        headers['Accept-Encoding'] = 'identity'

        yield from writer.awrite(b"%s /%s HTTP/1.1\r\n" % (method, path))
        if not "Host" in headers:
            yield from writer.awrite(b"Host: %s\r\n" % host)
        # Iterate over keys to avoid tuple alloc
        for k in headers:
            yield from writer.awrite(k)
            yield from writer.awrite(b": ")
            yield from writer.awrite(headers[k])
            yield from writer.awrite(b"\r\n")
        if json is not None:
            assert data is None
            import ujson
            data = ujson.dumps(json)
        if data:
            yield from writer.awrite(b"Content-Length: %d\r\n" % len(data))
        yield from writer.awrite(b"\r\n")
        if data:
            yield from writer.awrite(data)

        writer.aclose()

        l = yield from reader.readline()

        content_length = None

        protover, status, msg = l.split(None, 2)
        status = int(status)
        while True:
            l = s.readline()
            yield
            if not l or l == b"\r\n" or l == b'':
                break
            if l.startswith(b"Transfer-Encoding:"):
                if b"chunked" in l:
                    raise ValueError("Unsupported " + str(l))
            elif l.startswith(b"Location:") and not 200 <= status <= 299:
                raise NotImplementedError("Redirects not yet supported")
            elif l.startswith(b'Content-Length:'):
                content_length = int(l.split(None, 2)[1])
        resp = Response(s, content_length)
        resp.status_code = status
        resp.reason = msg.rstrip()
    except Exception as e:
        sys.print_exception(e)
        raise ValueError("Cannot receive data, check connection")
    gc.collect()
    return resp


def head(url, **kw):
    return request("HEAD", url, **kw)


def get(url, **kw):
    return request("GET", url, **kw)


def post(url, **kw):
    return request("POST", url, **kw)


def put(url, **kw):
    return request("PUT", url, **kw)


def patch(url, **kw):
    return request("PATCH", url, **kw)


def delete(url, **kw):
    return request("DELETE", url, **kw)


def get_json(url):
    r = yield from get(url)
    json_result = yield from r.json()
    r.close()
    return json_result


@coroutine
def client():
    while True:
        print("client:", "sleep 10 seconds")
        try:
            json_object = yield from get_json('https://triac-server.herokuapp.com/api/values')
            # json_object = yield from get_json('http://ip.jsontest.com/')
            print(json_object)
        except Exception as e:
            sys.print_exception(e)
            print("unable to fetch data, check connection")

        yield from sleepasyncio(5)


loop = get_event_loop()
loop.call_soon(client())
loop.run_forever()

Activity

  1. pfalcon commented on Sep 3, 2017

    @pfalcon
    Contributor
  2. robert-hh commented on Sep 3, 2017

    @robert-hh
    Contributor

    That's a very good essay, a pleasure to read.

  3. richarddd commented on Sep 6, 2017

    @richarddd
    Author

    @robert-hh @pfalcon nice read! See updated issue 🥇 👍

  4. pfalcon commented on Nov 3, 2017

    @pfalcon
    Contributor

    As far as I can tell, this issue is covered by #3394 . I'm thus closing this.

  5. added a commit that references this issue on Aug 20, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions