Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Livebook Freezes and Crashes #2158

Closed
Adzz opened this issue Aug 10, 2023 · 14 comments
Closed

Livebook Freezes and Crashes #2158

Adzz opened this issue Aug 10, 2023 · 14 comments
Labels
needs more info Needs more info on how to reproduce the issue

Comments

@Adzz
Copy link
Contributor

Adzz commented Aug 10, 2023

I'm not sure how to help reproduce this, but I have a livebook attached to an app via connected node. I run this cell:

from(sub in Db.SubMenu,
  where: sub.id == "01FXJX305X2MPZK5XFMVEK780F"
)
|> Repo.all()

Livebook freezes the cell never stops and I see this output (in the shell):

17:59:15.714 [error] GenServer #PID<0.610.0> terminating
** (Jason.EncodeError) invalid byte 0xBD in <<60, 115, 112, 97, 110, 32, 115, 116, 121, 108, 101, 61, 34, 99, 111, 108, 111, 114, 58, 32, 118, 97, 114, 40, 45, 45, 97, 110, 115, 105, 45, 99, 111, 108, 111, 114, 45, 114, 101, 100, 41, 59, 34, 62, 42, 42, 32, 40, 80, 111, ...>>
    (jason 1.4.0) lib/jason.ex:213: Jason.encode_to_iodata!/2
    (phoenix 1.7.5) lib/phoenix/socket/serializers/v2_json_serializer.ex:97: Phoenix.Socket.V2.JSONSerializer.encode!/1
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/channel.ex:868: Phoenix.LiveView.Channel.push/3
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/channel.ex:694: Phoenix.LiveView.Channel.handle_changed/4
    (stdlib 3.16.1) gen_server.erl:695: :gen_server.try_dispatch/4
    (stdlib 3.16.1) gen_server.erl:771: :gen_server.handle_msg/6
    (stdlib 3.16.1) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
Last message: {:operation, {:add_cell_evaluation_response, "__server__", "l2syb7t4wjoeirgg72oiwt3jv25zy7ie", {:error, <<27, 91, 51, 49, 109, 42, 42, 32, 40, 80, 111, 115, 116, 103, 114, 101, 120, 46, 69, 114, 114, 111, 114, 41, 32, 69, 82, 82, 79, 82, 32, 50, 50, 48, 50, 49, 32, 40, 99, 104, 97, 114, ...>>, :other}, %{code_markers: [], errored: true, evaluation_time_ms: 22, identifiers_defined: %{}, identifiers_used: :unknown, interrupted: false}}}
17:59:16.772 [error] Ranch listener LivebookWeb.Endpoint.HTTP had connection process started with :cowboy_clear:start_link/4 at #PID<0.567.0> exit with reason: {%Jason.EncodeError{message: "invalid byte 0xBD in <<60, 115, 112, 97, 110, 32, 115, 116, 121, 108, 101, 61, 34, 99, 111, 108, 111, 114, 58, 32, 118, 97, 114, 40, 45, 45, 97, 110, 115, 105, 45, 99, 111, 108, 111, 114, 45, 114, 101, 100, 41, 59, 34, 62, 42, 42, 32, 40, 80, 111, ...>>"}, [{Jason, :encode_to_iodata!, 2, [file: 'lib/jason.ex', line: 213, error_info: %{module: Exception}]}, {Phoenix.Socket.V2.JSONSerializer, :encode!, 1, [file: 'lib/phoenix/socket/serializers/v2_json_serializer.ex', line: 72]}, {Phoenix.Socket, :encode_reply, 2, [file: 'lib/phoenix/socket.ex', line: 745]}, {Phoenix.Socket, :handle_in, 4, [file: 'lib/phoenix/socket.ex', line: 642]}, {WebSockAdapter.CowboyAdapter, :websocket_handle, 2, [file: 'lib/websock_adapter/cowboy_adapter.ex', line: 24]}, {:cowboy_websocket, :handler_call, 6, [file: '/private/var/folders/w9/pjvl1qf94djfy15n7cj889r80000gn/T/mix-local-installer-fetcher-fM9K3g/deps/cowboy/src/cowboy_websocket.erl', line: 528]}, {:cowboy_http, :loop, 1, [file: '/private/var/folders/w9/pjvl1qf94djfy15n7cj889r80000gn/T/mix-local-installer-fetcher-fM9K3g/deps/cowboy/src/cowboy_http.erl', line: 257]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 226]}]}
image

Then an error log repeats indefinitely:

17:59:20.318 [error] Ranch listener LivebookWeb.Endpoint.HTTP had connection process started with :cowboy_clear:start_link/4 at #PID<0.668.0> exit with reason: {%Jason.EncodeError{message: "invalid byte 0xBD in <<60, 115, 112, 97, 110, 32, 115, 116, 121, 108, 101, 61, 34, 99, 111, 108, 111, 114, 58, 32, 118, 97, 114, 40, 45, 45, 97, 110, 115, 105, 45, 99, 111, 108, 111, 114, 45, 114, 101, 100, 41, 59, 34, 62, 42, 42, 32, 40, 80, 111, ...>>"}, [{Jason, :encode_to_iodata!, 2, [file: 'lib/jason.ex', line: 213, error_info: %{module: Exception}]}, {Phoenix.Socket.V2.JSONSerializer, :encode!, 1, [file: 'lib/phoenix/socket/serializers/v2_json_serializer.ex', line: 72]}, {Phoenix.Socket, :encode_reply, 2, [file: 'lib/phoenix/socket.ex', line: 745]}, {Phoenix.Socket, :handle_in, 4, [file: 'lib/phoenix/socket.ex', line: 642]}, {WebSockAdapter.CowboyAdapter, :websocket_handle, 2, [file: 'lib/websock_adapter/cowboy_adapter.ex', line: 24]}, {:cowboy_websocket, :handler_call, 6, [file: '/private/var/folders/w9/pjvl1qf94djfy15n7cj889r80000gn/T/mix-local-installer-fetcher-fM9K3g/deps/cowboy/src/cowboy_websocket.erl', line: 528]}, {:cowboy_http, :loop, 1, [file: '/private/var/folders/w9/pjvl1qf94djfy15n7cj889r80000gn/T/mix-local-installer-fetcher-fM9K3g/deps/cowboy/src/cowboy_http.erl', line: 257]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 226]}]}

What's really strange is if I do this in that same cell it's fine:

Repo.get(Db.SubMenu, "01FXJX305X2MPZK5XFMVEK780F")

Environment

  • Elixir & Erlang/OTP versions (elixir --version): Elixir 1.14.3
  • Operating system: mac os
  • How have you started Livebook (mix phx.server, livebook CLI, Docker, etc): livebook server . in shell
  • Livebook version (use git rev-parse HEAD if running with mix): latest
  • Browsers that reproduce this bug (the more the merrier): Firefox
  • Include what is logged in the browser console: --
  • Include what is logged to the server console: see above.
@jonatanklosko
Copy link
Member

Does it work if you add explicit |> inspect() at the end? Do you have any kino packages installed?

@Adzz
Copy link
Contributor Author

Adzz commented Aug 10, 2023

Okay if I pin the value then nothing crashes:

from(sub in Db.SubMenu,
  where: sub.id == ^"01FXJX305X2MPZK5XFMVEK780F"
)
|> Repo.all()

So it feels like:

  1. I'm miss-using ecto, but also
  2. it seems possible to have postgrex return an error and crash livebook? This is where I'm not sure how to help reproduce

No kino packages installed.

@jonatanklosko
Copy link
Member

jonatanklosko commented Aug 10, 2023

Does it happen for you with this notebook?

ecto.livemd
# Ecto

```elixir
Mix.install([
  {:ecto, "~> 3.6"},
  {:ecto_sql, "~> 3.6"},
  {:postgrex, "~> 0.15"}
])
```

## Setup

```
docker run --rm -it -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -p 5566:5432 postgres:13.2
```

## Query

```elixir
defmodule Repo do
  use Ecto.Repo, otp_app: :my_app, adapter: Ecto.Adapters.Postgres
end
```

```elixir
Repo.start_link(url: "postgres://postgres:postgres@localhost:5566/postgres")
```

```elixir
Ecto.Adapters.SQL.query!(Repo, "create table users (id uuid)")
```

```elixir
import Ecto.Query, only: [from: 2]

query =
  from(u in "users",
    where: u.id == "TMGz8OmBStKlnqgo",
    select: u.id
  )

Repo.all(query)
```

You can also try the exact ecto/postgrex versions your using.

@josevalim
Copy link
Contributor

@Adzz ping!

@josevalim josevalim added the needs more info Needs more info on how to reproduce the issue label Aug 24, 2023
@Adzz
Copy link
Contributor Author

Adzz commented Aug 24, 2023

Apologies, thanks for the ping!

The output if I run that is:

** (Postgrex.Error) ERROR 22P02 (invalid_text_representation) invalid input syntax for type uuid: "TMGz8OmBStKlnqgo". If you are trying to query a JSON field, the parameter may need to be interpolated. Instead of

    p.json["field"] != "value"

do

    p.json["field"] != ^"value"


    query: SELECT u0."id" FROM "users" AS u0 WHERE (u0."id" = 'TMGz8OmBStKlnqgo')
    (ecto_sql 3.10.2) lib/ecto/adapters/sql.ex:1047: Ecto.Adapters.SQL.raise_sql_call_error/1
    (ecto_sql 3.10.2) lib/ecto/adapters/sql.ex:945: Ecto.Adapters.SQL.execute/6
    (ecto 3.10.3) lib/ecto/repo/queryable.ex:229: Ecto.Repo.Queryable.execute/4
    (ecto 3.10.3) lib/ecto/repo/queryable.ex:19: Ecto.Repo.Queryable.all/3
    #cell:ekuvvpbi66cmrpqtjzrw5qun5jeingua:9: (file)

But livebook doesn't crash or hang

If I use a pin in your example it works:

import Ecto.Query, only: [from: 2]

query =
  from(u in "users",
    where: u.id == ^"TMGz8OmBStKlnqgo",
    select: u.id
  )

Repo.all(query)

returns []

@jonatanklosko
Copy link
Member

jonatanklosko commented Aug 24, 2023

I see, so it seems the reason it crashes is specific to your query. In that case it would be helpful if you could run Livebook main in dev mode and inspect the message here (for the original error):

defp render_error_message(message) do

@josevalim
Copy link
Contributor

Ping :)

@Adzz
Copy link
Contributor Author

Adzz commented Sep 30, 2023

I cloned main and ran the original livebook using it. I see a few things. The error that loops continuously is:

12:40:58.105 [error] Ranch listener LivebookWeb.Endpoint.HTTP had connection process started with :cowboy_clear:start_link/4 at #PID<0.1408.0> exit with reason: {%Jason.EncodeError{message: "invalid byte 0xBD in <<60, 115, 112, 97, 110, 32, 115, 116, 121, 108, 101, 61, 34, 99, 111, 108, 111, 114, 58, 32, 118, 97, 114, 40, 45, 45, 97, 110, 115, 105, 45, 99, 111, 108, 111, 114, 45, 114, 101, 100, 41, 59, 34, 62, 42, 42, 32, 40, 80, 111, ...>>"}, [{Jason, :encode_to_iodata!, 2, [file: 'lib/jason.ex', line: 213, error_info: %{module: Exception}]}, {Phoenix.Socket.V2.JSONSerializer, :encode!, 1, [file: 'lib/phoenix/socket/serializers/v2_json_serializer.ex', line: 72]}, {Phoenix.Socket, :encode_reply, 2, [file: 'lib/phoenix/socket.ex', line: 745]}, {Phoenix.Socket, :handle_in, 4, [file: 'lib/phoenix/socket.ex', line: 642]}, {WebSockAdapter.CowboyAdapter, :websocket_handle, 2, [file: 'lib/websock_adapter/cowboy_adapter.ex', line: 24]}, {:cowboy_websocket, :handler_call, 6, [file: '/Users/Adz/Projects/livebook/deps/cowboy/src/cowboy_websocket.erl', line: 528]}, {:cowboy_http, :loop, 1, [file: '/Users/Adz/Projects/livebook/deps/cowboy/src/cowboy_http.erl', line: 257]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 226]}]}

I think the crucial bit being :

[{Jason, :encode_to_iodata!, 2, [file: 'lib/jason.ex', line: 213, error_info: %{module: Exception}]}, {Phoenix.Socket.V2.JSONSerializer, :encode!, 1,

The logged message is:

<<27, 91, 51, 49, 109, 42, 42, 32, 40, 80, 111, 115, 116, 103, 114, 101, 120,
  46, 69, 114, 114, 111, 114, 41, 32, 69, 82, 82, 79, 82, 32, 50, 50, 48, 50,
  49, 32, 40, 99, 104, 97, 114, 97, 99, 116, 101, 114, 95, 110, 111, 116, 95,
  105, 110, 95, 114, 101, 112, 101, 114, 116, 111, 105, 114, 101, 41, 32, 105,
  110, 118, 97, 108, 105, 100, 32, 98, 121, 116, 101, 32, 115, 101, 113, 117,
  101, 110, 99, 101, 32, 102, 111, 114, 32, 101, 110, 99, 111, 100, 105, 110,
  103, 32, 34, 85, 84, 70, 56, 34, 58, 32, 48, 120, 98, 100, 10, 10, 32, 32, 32,
  32, 113, 117, 101, 114, 121, 58, 32, 83, 69, 76, 69, 67, 84, 32, 115, 48, 46,
  34, 105, 100, 34, 44, 32, 115, 48, 46, 34, 110, 97, 109, 101, 34, 44, 32, 115,
  48, 46, 34, 111, 114, 100, 101, 114, 34, 44, 32, 115, 48, 46, 34, 109, 101,
  110, 117, 95, 105, 100, 34, 44, 32, 115, 48, 46, 34, 105, 110, 115, 101, 114,
  116, 101, 100, 95, 97, 116, 34, 44, 32, 115, 48, 46, 34, 117, 112, 100, 97,
  116, 101, 100, 95, 97, 116, 34, 32, 70, 82, 79, 77, 32, 34, 115, 117, 98, 95,
  109, 101, 110, 117, 115, 34, 32, 65, 83, 32, 115, 48, 32, 87, 72, 69, 82, 69,
  32, 40, 115, 48, 46, 34, 105, 100, 34, 32, 61, 32, 39, 1, 127, 101, 209, 128,
  189, 21, 45, 249, 151, 175, 166, 221, 51, 160, 15, 39, 41, 27, 91, 48, 109,
  10, 27, 91, 51, 49, 109, 32, 32, 32, 32, 40, 101, 99, 116, 111, 95, 115, 113,
  108, 32, 51, 46, 57, 46, 48, 41, 32, 108, 105, 98, 47, 101, 99, 116, 111, 47,
  97, 100, 97, 112, 116, 101, 114, 115, 47, 115, 113, 108, 46, 101, 120, 58, 57,
  48, 53, 58, 32, 69, 99, 116, 111, 46, 65, 100, 97, 112, 116, 101, 114, 115,
  46, 83, 81, 76, 46, 114, 97, 105, 115, 101, 95, 115, 113, 108, 95, 99, 97,
  108, 108, 95, 101, 114, 114, 111, 114, 47, 49, 10, 32, 32, 32, 32, 40, 101,
  99, 116, 111, 95, 115, 113, 108, 32, 51, 46, 57, 46, 48, 41, 32, 108, 105, 98,
  47, 101, 99, 116, 111, 47, 97, 100, 97, 112, 116, 101, 114, 115, 47, 115, 113,
  108, 46, 101, 120, 58, 56, 50, 48, 58, 32, 69, 99, 116, 111, 46, 65, 100, 97,
  112, 116, 101, 114, 115, 46, 83, 81, 76, 46, 101, 120, 101, 99, 117, 116, 101,
  47, 54, 10, 32, 32, 32, 32, 40, 101, 99, 116, 111, 32, 51, 46, 57, 46, 49, 41,
  32, 108, 105, 98, 47, 101, 99, 116, 111, 47, 114, 101, 112, 111, 47, 113, 117,
  101, 114, 121, 97, 98, 108, 101, 46, 101, 120, 58, 50, 50, 57, 58, 32, 69, 99,
  116, 111, 46, 82, 101, 112, 111, 46, 81, 117, 101, 114, 121, 97, 98, 108, 101,
  46, 101, 120, 101, 99, 117, 116, 101, 47, 52, 10, 32, 32, 32, 32, 40, 101, 99,
  116, 111, 32, 51, 46, 57, 46, 49, 41, 32, 108, 105, 98, 47, 101, 99, 116, 111,
  47, 114, 101, 112, 111, 47, 113, 117, 101, 114, 121, 97, 98, 108, 101, 46,
  101, 120, 58, 49, 57, 58, 32, 69, 99, 116, 111, 46, 82, 101, 112, 111, 46, 81,
  117, 101, 114, 121, 97, 98, 108, 101, 46, 97, 108, 108, 47, 51, 10, 32, 32,
  32, 32, 35, 99, 101, 108, 108, 58, 114, 99, 110, 111, 115, 107, 108, 120, 117,
  54, 116, 103, 102, 55, 116, 55, 103, 55, 117, 111, 106, 97, 100, 97, 121, 97,
  55, 121, 50, 110, 119, 109, 58, 52, 58, 32, 40, 102, 105, 108, 101, 41, 10,
  32, 32, 32, 32, 40, 115, 116, 100, 108, 105, 98, 32, 52, 46, 49, 46, 49, 41,
  32, 101, 114, 108, 95, 101, 118, 97, 108, 46, 101, 114, 108, 58, 55, 52, 56,
  58, 32, 58, 101, 114, 108, 95, 101, 118, 97, 108, 46, 100, 111, 95, 97, 112,
  112, 108, 121, 47, 55, 10, 32, 32, 32, 32, 40, 101, 108, 105, 120, 105, 114,
  32, 49, 46, 49, 52, 46, 50, 41, 32, 108, 105, 98, 47, 109, 111, 100, 117, 108,
  101, 47, 112, 97, 114, 97, 108, 108, 101, 108, 95, 99, 104, 101, 99, 107, 101,
  114, 46, 101, 120, 58, 49, 48, 55, 58, 32, 77, 111, 100, 117, 108, 101, 46,
  80, 97, 114, 97, 108, 108, 101, 108, 67, 104, 101, 99, 107, 101, 114, 46, 118,
  101, 114, 105, 102, 121, 47, 49, 10, 27, 91, 48, 109>>

Which makes it seem like there are non valid JSON chars in the error message?

@Adzz
Copy link
Contributor Author

Adzz commented Sep 30, 2023

Doing this:

 String.codepoints(message) |> Enum.filter(&String.printable?/1) |> Enum.join() |> IO.puts

Yields:

** (Postgrex.Error) ERROR 22021 (character_not_in_repertoire) invalid byte sequence for encoding "UTF8": 0xbd

    query: SELECT s0."id", s0."name", s0."order", s0."menu_id", s0."inserted_at", s0."updated_at" FROM "sub_menus" AS s0 WHERE (s0."id" = '^�eр-3')
    (ecto_sql 3.9.0) lib/ecto/adapters/sql.ex:905: Ecto.Adapters.SQL.raise_sql_call_error/1
    (ecto_sql 3.9.0) lib/ecto/adapters/sql.ex:820: Ecto.Adapters.SQL.execute/6
    (ecto 3.9.1) lib/ecto/repo/queryable.ex:229: Ecto.Repo.Queryable.execute/4
    (ecto 3.9.1) lib/ecto/repo/queryable.ex:19: Ecto.Repo.Queryable.all/3
    #cell:rcnosklxu6tgf7t7g7uojadaya7y2nwm:4: (file)
    (stdlib 4.1.1) erl_eval.erl:748: :erl_eval.do_apply/7
    (elixir 1.14.2) lib/module/parallel_checker.ex:107: Module.ParallelChecker.verify/1

@josevalim
Copy link
Contributor

Thank you! I have some thoughts about this.

What is the type for the ID field of Db.SubMenu? I have tried reproducing this issue in Ecto, using :uuid or :binary_id, and it precisely tells me to use ^:

  1) test binary parameter (Ecto.Adapters.PostgresTest)
     test/ecto/adapters/postgres_test.exs:1091
     ** (Ecto.QueryError) test/ecto/adapters/postgres_test.exs:1093: value `"01FXJX305X2MPZK5XFMVEK780F"` cannot be dumped to type :binary_id. Or the value is incompatible or it must be interpolated (using ^) so it may be cast accordingly in query:

     from s0 in Ecto.Adapters.PostgresTest.Schema3,
       where: s0.id == "01FXJX305X2MPZK5XFMVEK780F",
       select: true

if it is a binary, it gets interpolated as a bytea:

"SELECT TRUE FROM \"schema3\" AS s0 WHERE (s0.\"id\" = '\\x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'::bytea)"

which is a valid SQL queries.

In any case, it is clear we are failing to encode the error message, so we need to either allow more data to be encoded (which would help this issue: livebook-dev/kino_db#56) or we would need to add similar safe-guards.

Btw, @jonatanklosko, I had an idea! Instead of doing our own Jason encoding and then sending it as binary in js_view_channel, perhaps we could do our own encoding and wrap it inside a Jason.Fragment?

@Adzz
Copy link
Contributor Author

Adzz commented Sep 30, 2023

The ID is a custom ecto type, a ULID: (adapted from here

defmodule Ecto.ULID do
  @moduledoc """
  An Ecto type for ULID strings. This is copied from https://github.com/TheRealReal/ecto-ulid
  which seems unmaintained. Should probably fork and make it a package.
  """

  use Ecto.Type

  @doc """
  The underlying schema type.
  """
  def type, do: :uuid

  def embed_as(_), do: :self
  def equal?(term1, term2), do: term1 == term2

  @doc """
  Casts a string to ULID.
  """
  def cast(<<_::bytes-size(26)>> = value) do
    if valid?(value) do
      {:ok, value}
    else
      :error
    end
  end

  def cast(_), do: :error

  @doc """
  Same as `cast/1` but raises `Ecto.CastError` on invalid arguments.
  """
  def cast!(value) do
    case cast(value) do
      {:ok, ulid} -> ulid
      :error -> raise Ecto.CastError, type: __MODULE__, value: value
    end
  end

  @doc """
  Converts a Crockford Base32 encoded ULID into a binary.
  """
  def dump(<<_::bytes-size(26)>> = encoded), do: decode(encoded)
  def dump(_), do: :error

  @doc """
  Converts a binary ULID into a Crockford Base32 encoded string.
  """
  def load(<<_::unsigned-size(128)>> = bytes), do: encode(bytes)
  def load(_), do: :error

  @doc false
  def autogenerate, do: generate()

  @doc """
  Generates a Crockford Base32 encoded ULID.

  If a value is provided for `timestamp`, the generated ULID will be for the provided timestamp.
  Otherwise, a ULID will be generated for the current time.

  Arguments:

  * `timestamp`: A Unix timestamp with millisecond precision.
  """
  def generate(timestamp \\ System.system_time(:millisecond)) do
    {:ok, ulid} = encode(bingenerate(timestamp))
    ulid
  end

  @doc """
  Generates a binary ULID.

  If a value is provided for `timestamp`, the generated ULID will be for the provided timestamp.
  Otherwise, a ULID will be generated for the current time.

  Arguments:

  * `timestamp`: A Unix timestamp with millisecond precision.
  """
  def bingenerate(timestamp \\ System.system_time(:millisecond)) do
    <<timestamp::unsigned-size(48), :crypto.strong_rand_bytes(10)::binary>>
  end

  defp encode(
         <<b1::3, b2::5, b3::5, b4::5, b5::5, b6::5, b7::5, b8::5, b9::5, b10::5, b11::5, b12::5,
           b13::5, b14::5, b15::5, b16::5, b17::5, b18::5, b19::5, b20::5, b21::5, b22::5, b23::5,
           b24::5, b25::5, b26::5>>
       ) do
    <<e(b1), e(b2), e(b3), e(b4), e(b5), e(b6), e(b7), e(b8), e(b9), e(b10), e(b11), e(b12),
      e(b13), e(b14), e(b15), e(b16), e(b17), e(b18), e(b19), e(b20), e(b21), e(b22), e(b23),
      e(b24), e(b25), e(b26)>>
  catch
    :error -> :error
  else
    encoded -> {:ok, encoded}
  end

  defp encode(_), do: :error

  @compile {:inline, e: 1}

  defp e(0), do: ?0
  defp e(1), do: ?1
  defp e(2), do: ?2
  defp e(3), do: ?3
  defp e(4), do: ?4
  defp e(5), do: ?5
  defp e(6), do: ?6
  defp e(7), do: ?7
  defp e(8), do: ?8
  defp e(9), do: ?9
  defp e(10), do: ?A
  defp e(11), do: ?B
  defp e(12), do: ?C
  defp e(13), do: ?D
  defp e(14), do: ?E
  defp e(15), do: ?F
  defp e(16), do: ?G
  defp e(17), do: ?H
  defp e(18), do: ?J
  defp e(19), do: ?K
  defp e(20), do: ?M
  defp e(21), do: ?N
  defp e(22), do: ?P
  defp e(23), do: ?Q
  defp e(24), do: ?R
  defp e(25), do: ?S
  defp e(26), do: ?T
  defp e(27), do: ?V
  defp e(28), do: ?W
  defp e(29), do: ?X
  defp e(30), do: ?Y
  defp e(31), do: ?Z

  defp decode(
         <<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8, c9::8, c10::8, c11::8, c12::8,
           c13::8, c14::8, c15::8, c16::8, c17::8, c18::8, c19::8, c20::8, c21::8, c22::8, c23::8,
           c24::8, c25::8, c26::8>>
       ) do
    <<d(c1)::3, d(c2)::5, d(c3)::5, d(c4)::5, d(c5)::5, d(c6)::5, d(c7)::5, d(c8)::5, d(c9)::5,
      d(c10)::5, d(c11)::5, d(c12)::5, d(c13)::5, d(c14)::5, d(c15)::5, d(c16)::5, d(c17)::5,
      d(c18)::5, d(c19)::5, d(c20)::5, d(c21)::5, d(c22)::5, d(c23)::5, d(c24)::5, d(c25)::5,
      d(c26)::5>>
  catch
    :error -> :error
  else
    decoded -> {:ok, decoded}
  end

  defp decode(_), do: :error

  @compile {:inline, d: 1}

  defp d(?0), do: 0
  defp d(?1), do: 1
  defp d(?2), do: 2
  defp d(?3), do: 3
  defp d(?4), do: 4
  defp d(?5), do: 5
  defp d(?6), do: 6
  defp d(?7), do: 7
  defp d(?8), do: 8
  defp d(?9), do: 9
  defp d(?A), do: 10
  defp d(?B), do: 11
  defp d(?C), do: 12
  defp d(?D), do: 13
  defp d(?E), do: 14
  defp d(?F), do: 15
  defp d(?G), do: 16
  defp d(?H), do: 17
  defp d(?J), do: 18
  defp d(?K), do: 19
  defp d(?M), do: 20
  defp d(?N), do: 21
  defp d(?P), do: 22
  defp d(?Q), do: 23
  defp d(?R), do: 24
  defp d(?S), do: 25
  defp d(?T), do: 26
  defp d(?V), do: 27
  defp d(?W), do: 28
  defp d(?X), do: 29
  defp d(?Y), do: 30
  defp d(?Z), do: 31
  defp d(_), do: throw(:error)

  defp valid?(
         <<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8, c9::8, c10::8, c11::8, c12::8,
           c13::8, c14::8, c15::8, c16::8, c17::8, c18::8, c19::8, c20::8, c21::8, c22::8, c23::8,
           c24::8, c25::8, c26::8>>
       ) do
    v(c1) && v(c2) && v(c3) && v(c4) && v(c5) && v(c6) && v(c7) && v(c8) && v(c9) && v(c10) &&
      v(c11) && v(c12) && v(c13) &&
      v(c14) && v(c15) && v(c16) && v(c17) && v(c18) && v(c19) && v(c20) && v(c21) && v(c22) &&
      v(c23) && v(c24) && v(c25) && v(c26)
  end

  defp valid?(_), do: false

  @compile {:inline, v: 1}

  defp v(?0), do: true
  defp v(?1), do: true
  defp v(?2), do: true
  defp v(?3), do: true
  defp v(?4), do: true
  defp v(?5), do: true
  defp v(?6), do: true
  defp v(?7), do: true
  defp v(?8), do: true
  defp v(?9), do: true
  defp v(?A), do: true
  defp v(?B), do: true
  defp v(?C), do: true
  defp v(?D), do: true
  defp v(?E), do: true
  defp v(?F), do: true
  defp v(?G), do: true
  defp v(?H), do: true
  defp v(?J), do: true
  defp v(?K), do: true
  defp v(?M), do: true
  defp v(?N), do: true
  defp v(?P), do: true
  defp v(?Q), do: true
  defp v(?R), do: true
  defp v(?S), do: true
  defp v(?T), do: true
  defp v(?V), do: true
  defp v(?W), do: true
  defp v(?X), do: true
  defp v(?Y), do: true
  defp v(?Z), do: true
  defp v(_), do: false
end

@josevalim
Copy link
Contributor

Ah, perfect. I got the underlying UUID, converted it to "017f65d1-80bd-152d-f997-afa6dd33a00f" and I could reproduce the issue in Ecto. :)

@josevalim
Copy link
Contributor

elixir-ecto/ecto#4289

@josevalim
Copy link
Contributor

Closing this for now. The discussion can now continue on livebook-dev/kino_db#56.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs more info Needs more info on how to reproduce the issue
Projects
None yet
Development

No branches or pull requests

3 participants