From c0e13e85fbee08952e4b416e9802713a08203c37 Mon Sep 17 00:00:00 2001 From: Jameel Al-Aziz Date: Wed, 29 Dec 2021 05:43:19 -0800 Subject: [PATCH 1/9] Fix UnicodeDecodeError in aioredis integration Escape non-unicode bytes when decoding aioredis args. This prevents the `UnicodeDecodeError` while still offering some semblance of a human-readable string for tracing. --- ddtrace/contrib/aioredis/patch.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ddtrace/contrib/aioredis/patch.py b/ddtrace/contrib/aioredis/patch.py index fad1f471525..709466fe09e 100644 --- a/ddtrace/contrib/aioredis/patch.py +++ b/ddtrace/contrib/aioredis/patch.py @@ -66,7 +66,7 @@ async def traced_execute_command(func, instance, args, kwargs): if not pin or not pin.enabled(): return await func(*args, **kwargs) - decoded_args = [arg.decode() if isinstance(arg, bytes) else arg for arg in args] + decoded_args = [arg.decode(errors="backslashreplace") if isinstance(arg, bytes) else arg for arg in args] with _trace_redis_cmd(pin, config.aioredis, instance, decoded_args): return await func(*args, **kwargs) @@ -107,7 +107,7 @@ def traced_13_execute_command(func, instance, args, kwargs): if not pin or not pin.enabled(): return func(*args, **kwargs) - decoded_args = [arg.decode() if isinstance(arg, bytes) else arg for arg in args] + decoded_args = [arg.decode(errors="backslashreplace") if isinstance(arg, bytes) else arg for arg in args] # Don't activate the span since this operation is performed as a future which concludes sometime later on in # execution so subsequent operations in the stack are not necessarily semantically related # (we don't want this span to be the parent of all other spans created before the future is resolved) @@ -157,7 +157,7 @@ async def traced_13_execute_pipeline(func, instance, args, kwargs): cmds = [] for _, cmd, cmd_args, _ in instance._pipeline: - parts = [cmd.decode() if isinstance(cmd, bytes) else cmd] + parts = [cmd.decode(errors="backslashreplace") if isinstance(cmd, bytes) else cmd] parts.extend(cmd_args) cmds.append(format_command_args(parts)) resource = "\n".join(cmds) From 6785142df744e022f8b000a9b14e88cdd2a42e67 Mon Sep 17 00:00:00 2001 From: Jameel Al-Aziz Date: Wed, 29 Dec 2021 15:17:43 -0800 Subject: [PATCH 2/9] remove unnecessary errors arg to decode --- ddtrace/contrib/aioredis/patch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddtrace/contrib/aioredis/patch.py b/ddtrace/contrib/aioredis/patch.py index 709466fe09e..8786c57ed91 100644 --- a/ddtrace/contrib/aioredis/patch.py +++ b/ddtrace/contrib/aioredis/patch.py @@ -157,7 +157,7 @@ async def traced_13_execute_pipeline(func, instance, args, kwargs): cmds = [] for _, cmd, cmd_args, _ in instance._pipeline: - parts = [cmd.decode(errors="backslashreplace") if isinstance(cmd, bytes) else cmd] + parts = [cmd.decode() if isinstance(cmd, bytes) else cmd] parts.extend(cmd_args) cmds.append(format_command_args(parts)) resource = "\n".join(cmds) From 232ce84a2d517cdefdf4f14ef3391a6e8acc9785 Mon Sep 17 00:00:00 2001 From: Jameel Al-Aziz Date: Wed, 29 Dec 2021 15:18:01 -0800 Subject: [PATCH 3/9] add tests --- tests/contrib/aioredis/test_aioredis.py | 31 ++++++++++ ..._aioredis.test_decoding_non_utf8_args.json | 56 +++++++++++++++++++ ....test_decoding_non_utf8_pipeline_args.json | 28 ++++++++++ ...st_decoding_non_utf8_pipeline_args_13.json | 28 ++++++++++ 4 files changed, 143 insertions(+) create mode 100644 tests/snapshots/tests.contrib.aioredis.test_aioredis.test_decoding_non_utf8_args.json create mode 100644 tests/snapshots/tests.contrib.aioredis.test_aioredis.test_decoding_non_utf8_pipeline_args.json create mode 100644 tests/snapshots/tests.contrib.aioredis.test_aioredis.test_decoding_non_utf8_pipeline_args_13.json diff --git a/tests/contrib/aioredis/test_aioredis.py b/tests/contrib/aioredis/test_aioredis.py index 3ef8780bcdd..33e736c2345 100644 --- a/tests/contrib/aioredis/test_aioredis.py +++ b/tests/contrib/aioredis/test_aioredis.py @@ -66,6 +66,37 @@ async def test_basic_request(redis_client): assert val is None +@pytest.mark.asyncio +@pytest.mark.snapshot +async def test_decoding_non_utf8_args(redis_client): + await redis_client.set(b"\x80foo", b"\x80abc") + val = await redis_client.get(b"\x80foo") + assert val == b"\x80abc" + + +@pytest.mark.asyncio +@pytest.mark.snapshot(variants={"": aioredis_version >= (2, 0), "13": aioredis_version < (2, 0)}) +async def test_decoding_non_utf8_pipeline_args(redis_client): + if aioredis_version >= (2, 0): + p = await redis_client.pipeline(transaction=False) + await p.set(b"\x80blah", "boo") + await p.set("foo", b"\x80abc") + await p.get(b"\x80blah") + await p.get("foo") + else: + p = redis_client.pipeline() + p.set(b"\x80blah", "boo") + p.set("foo", b"\x80abc") + p.get(b"\x80blah") + p.get("foo") + + response_list = await p.execute() + assert response_list[0] is True # response from redis.set is OK if successfully pushed + assert response_list[1] is True + assert response_list[2].decode() == "boo" + assert response_list[3] == b"\x80abc" + + @pytest.mark.asyncio @pytest.mark.snapshot async def test_long_command(redis_client): diff --git a/tests/snapshots/tests.contrib.aioredis.test_aioredis.test_decoding_non_utf8_args.json b/tests/snapshots/tests.contrib.aioredis.test_aioredis.test_decoding_non_utf8_args.json new file mode 100644 index 00000000000..dd61ecdf975 --- /dev/null +++ b/tests/snapshots/tests.contrib.aioredis.test_aioredis.test_decoding_non_utf8_args.json @@ -0,0 +1,56 @@ +[[ + { + "name": "redis.command", + "service": "redis", + "resource": "SET \\x80foo \\x80abc", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "redis", + "meta": { + "out.host": "127.0.0.1", + "redis.raw_command": "SET \\x80foo \\x80abc", + "runtime-id": "af49d807abff41089eed43f05d099627" + }, + "metrics": { + "_dd.agent_psr": 1.0, + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "out.port": 6379, + "out.redis_db": 0, + "redis.args_length": 3, + "system.pid": 36293 + }, + "duration": 6351000, + "start": 1640819680842990000 + }], +[ + { + "name": "redis.command", + "service": "redis", + "resource": "GET \\x80foo", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "redis", + "meta": { + "out.host": "127.0.0.1", + "redis.raw_command": "GET \\x80foo", + "runtime-id": "af49d807abff41089eed43f05d099627" + }, + "metrics": { + "_dd.agent_psr": 1.0, + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "out.port": 6379, + "out.redis_db": 0, + "redis.args_length": 2, + "system.pid": 36293 + }, + "duration": 1959000, + "start": 1640819680849456000 + }]] diff --git a/tests/snapshots/tests.contrib.aioredis.test_aioredis.test_decoding_non_utf8_pipeline_args.json b/tests/snapshots/tests.contrib.aioredis.test_aioredis.test_decoding_non_utf8_pipeline_args.json new file mode 100644 index 00000000000..76ddf8fb40e --- /dev/null +++ b/tests/snapshots/tests.contrib.aioredis.test_aioredis.test_decoding_non_utf8_pipeline_args.json @@ -0,0 +1,28 @@ +[[ + { + "name": "redis.command", + "service": "redis", + "resource": "SET b'\\x80blah' boo\nSET foo b'\\x80abc'\nGET b'\\x80blah'\nGET foo", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "redis", + "meta": { + "out.host": "127.0.0.1", + "redis.raw_command": "SET b'\\x80blah' boo\nSET foo b'\\x80abc'\nGET b'\\x80blah'\nGET foo", + "runtime-id": "09a114cae7784254a3efa0ae78176d37" + }, + "metrics": { + "_dd.agent_psr": 1.0, + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "out.port": 6379, + "out.redis_db": 0, + "redis.pipeline_length": 4, + "system.pid": 36328 + }, + "duration": 2772000, + "start": 1640819684644133000 + }]] diff --git a/tests/snapshots/tests.contrib.aioredis.test_aioredis.test_decoding_non_utf8_pipeline_args_13.json b/tests/snapshots/tests.contrib.aioredis.test_aioredis.test_decoding_non_utf8_pipeline_args_13.json new file mode 100644 index 00000000000..77d7e949cc9 --- /dev/null +++ b/tests/snapshots/tests.contrib.aioredis.test_aioredis.test_decoding_non_utf8_pipeline_args_13.json @@ -0,0 +1,28 @@ +[[ + { + "name": "redis.command", + "service": "redis", + "resource": "SET b'\\x80blah' boo\nSET foo b'\\x80abc'\nGET b'\\x80blah'\nGET foo", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "redis", + "meta": { + "out.host": "127.0.0.1", + "redis.raw_command": "SET b'\\x80blah' boo\nSET foo b'\\x80abc'\nGET b'\\x80blah'\nGET foo", + "runtime-id": "af49d807abff41089eed43f05d099627" + }, + "metrics": { + "_dd.agent_psr": 1.0, + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "out.port": 6379, + "out.redis_db": 0, + "redis.pipeline_length": 4, + "system.pid": 36293 + }, + "duration": 3099000, + "start": 1640819680991289000 + }]] From bf3d1cc2a5264113dced9c86d31cafd62166b44f Mon Sep 17 00:00:00 2001 From: Jameel Al-Aziz Date: Thu, 30 Dec 2021 16:55:06 -0800 Subject: [PATCH 4/9] Use ensure_text instead of stringify in some situations --- ddtrace/contrib/aioredis/patch.py | 10 ++++------ ddtrace/contrib/redis/util.py | 7 +++++-- ...ioredis.test_decoding_non_utf8_pipeline_args.json | 12 ++++++------ ...edis.test_decoding_non_utf8_pipeline_args_13.json | 12 ++++++------ 4 files changed, 21 insertions(+), 20 deletions(-) diff --git a/ddtrace/contrib/aioredis/patch.py b/ddtrace/contrib/aioredis/patch.py index 8786c57ed91..c2752835c9f 100644 --- a/ddtrace/contrib/aioredis/patch.py +++ b/ddtrace/contrib/aioredis/patch.py @@ -66,8 +66,7 @@ async def traced_execute_command(func, instance, args, kwargs): if not pin or not pin.enabled(): return await func(*args, **kwargs) - decoded_args = [arg.decode(errors="backslashreplace") if isinstance(arg, bytes) else arg for arg in args] - with _trace_redis_cmd(pin, config.aioredis, instance, decoded_args): + with _trace_redis_cmd(pin, config.aioredis, instance, args): return await func(*args, **kwargs) @@ -107,7 +106,6 @@ def traced_13_execute_command(func, instance, args, kwargs): if not pin or not pin.enabled(): return func(*args, **kwargs) - decoded_args = [arg.decode(errors="backslashreplace") if isinstance(arg, bytes) else arg for arg in args] # Don't activate the span since this operation is performed as a future which concludes sometime later on in # execution so subsequent operations in the stack are not necessarily semantically related # (we don't want this span to be the parent of all other spans created before the future is resolved) @@ -116,7 +114,7 @@ def traced_13_execute_command(func, instance, args, kwargs): ) span.set_tag(SPAN_MEASURED_KEY) - query = format_command_args(decoded_args) + query = format_command_args(args) span.resource = query span.set_tag(redisx.RAWCMD, query) if pin.tags: @@ -129,7 +127,7 @@ def traced_13_execute_command(func, instance, args, kwargs): redisx.DB: instance.db or 0, } ) - span.set_metric(redisx.ARGS_LEN, len(decoded_args)) + span.set_metric(redisx.ARGS_LEN, len(args)) # set analytics sample rate if enabled span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.aioredis.get_analytics_sample_rate()) @@ -157,7 +155,7 @@ async def traced_13_execute_pipeline(func, instance, args, kwargs): cmds = [] for _, cmd, cmd_args, _ in instance._pipeline: - parts = [cmd.decode() if isinstance(cmd, bytes) else cmd] + parts = [cmd] parts.extend(cmd_args) cmds.append(format_command_args(parts)) resource = "\n".join(cmds) diff --git a/ddtrace/contrib/redis/util.py b/ddtrace/contrib/redis/util.py index e4ad398bbe7..d0b74bce409 100644 --- a/ddtrace/contrib/redis/util.py +++ b/ddtrace/contrib/redis/util.py @@ -9,7 +9,7 @@ from ...ext import SpanTypes from ...ext import net from ...ext import redis as redisx -from ...internal.compat import stringify +from ...internal.compat import ensure_text, stringify VALUE_PLACEHOLDER = "?" @@ -41,7 +41,10 @@ def format_command_args(args): out = [] for arg in args: try: - cmd = stringify(arg) + try: + cmd = ensure_text(arg, errors="backslashreplace") + except TypeError: + cmd = stringify(arg) if len(cmd) > VALUE_MAX_LEN: cmd = cmd[:VALUE_MAX_LEN] + VALUE_TOO_LONG_MARK diff --git a/tests/snapshots/tests.contrib.aioredis.test_aioredis.test_decoding_non_utf8_pipeline_args.json b/tests/snapshots/tests.contrib.aioredis.test_aioredis.test_decoding_non_utf8_pipeline_args.json index 76ddf8fb40e..2820d79c1ff 100644 --- a/tests/snapshots/tests.contrib.aioredis.test_aioredis.test_decoding_non_utf8_pipeline_args.json +++ b/tests/snapshots/tests.contrib.aioredis.test_aioredis.test_decoding_non_utf8_pipeline_args.json @@ -2,15 +2,15 @@ { "name": "redis.command", "service": "redis", - "resource": "SET b'\\x80blah' boo\nSET foo b'\\x80abc'\nGET b'\\x80blah'\nGET foo", + "resource": "SET \\x80blah boo\nSET foo \\x80abc\nGET \\x80blah\nGET foo", "trace_id": 0, "span_id": 1, "parent_id": 0, "type": "redis", "meta": { "out.host": "127.0.0.1", - "redis.raw_command": "SET b'\\x80blah' boo\nSET foo b'\\x80abc'\nGET b'\\x80blah'\nGET foo", - "runtime-id": "09a114cae7784254a3efa0ae78176d37" + "redis.raw_command": "SET \\x80blah boo\nSET foo \\x80abc\nGET \\x80blah\nGET foo", + "runtime-id": "e6362b13a7394c2490fc39fa29c0e4af" }, "metrics": { "_dd.agent_psr": 1.0, @@ -21,8 +21,8 @@ "out.port": 6379, "out.redis_db": 0, "redis.pipeline_length": 4, - "system.pid": 36328 + "system.pid": 60727 }, - "duration": 2772000, - "start": 1640819684644133000 + "duration": 922000, + "start": 1640911687480019000 }]] diff --git a/tests/snapshots/tests.contrib.aioredis.test_aioredis.test_decoding_non_utf8_pipeline_args_13.json b/tests/snapshots/tests.contrib.aioredis.test_aioredis.test_decoding_non_utf8_pipeline_args_13.json index 77d7e949cc9..1cbf6466aec 100644 --- a/tests/snapshots/tests.contrib.aioredis.test_aioredis.test_decoding_non_utf8_pipeline_args_13.json +++ b/tests/snapshots/tests.contrib.aioredis.test_aioredis.test_decoding_non_utf8_pipeline_args_13.json @@ -2,15 +2,15 @@ { "name": "redis.command", "service": "redis", - "resource": "SET b'\\x80blah' boo\nSET foo b'\\x80abc'\nGET b'\\x80blah'\nGET foo", + "resource": "SET \\x80blah boo\nSET foo \\x80abc\nGET \\x80blah\nGET foo", "trace_id": 0, "span_id": 1, "parent_id": 0, "type": "redis", "meta": { "out.host": "127.0.0.1", - "redis.raw_command": "SET b'\\x80blah' boo\nSET foo b'\\x80abc'\nGET b'\\x80blah'\nGET foo", - "runtime-id": "af49d807abff41089eed43f05d099627" + "redis.raw_command": "SET \\x80blah boo\nSET foo \\x80abc\nGET \\x80blah\nGET foo", + "runtime-id": "2c1fc87edd3b44ea8e46fe3854500d3c" }, "metrics": { "_dd.agent_psr": 1.0, @@ -21,8 +21,8 @@ "out.port": 6379, "out.redis_db": 0, "redis.pipeline_length": 4, - "system.pid": 36293 + "system.pid": 60927 }, - "duration": 3099000, - "start": 1640819680991289000 + "duration": 982000, + "start": 1640911730323059000 }]] From 64cd8a36391d478d235ea5100287212d4abba32a Mon Sep 17 00:00:00 2001 From: Jameel Al-Aziz Date: Wed, 5 Jan 2022 13:50:09 -0800 Subject: [PATCH 5/9] PR feedback --- ddtrace/contrib/aioredis/patch.py | 3 +-- ddtrace/contrib/redis/util.py | 6 +++--- ddtrace/internal/compat.py | 1 + 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ddtrace/contrib/aioredis/patch.py b/ddtrace/contrib/aioredis/patch.py index c2752835c9f..5d90663aa9c 100644 --- a/ddtrace/contrib/aioredis/patch.py +++ b/ddtrace/contrib/aioredis/patch.py @@ -155,8 +155,7 @@ async def traced_13_execute_pipeline(func, instance, args, kwargs): cmds = [] for _, cmd, cmd_args, _ in instance._pipeline: - parts = [cmd] - parts.extend(cmd_args) + parts = [cmd] + cmd_args cmds.append(format_command_args(parts)) resource = "\n".join(cmds) with pin.tracer.trace( diff --git a/ddtrace/contrib/redis/util.py b/ddtrace/contrib/redis/util.py index d0b74bce409..1a608cfe13c 100644 --- a/ddtrace/contrib/redis/util.py +++ b/ddtrace/contrib/redis/util.py @@ -9,7 +9,7 @@ from ...ext import SpanTypes from ...ext import net from ...ext import redis as redisx -from ...internal.compat import ensure_text, stringify +from ...internal.compat import binary_type, ensure_text, stringify, text_type VALUE_PLACEHOLDER = "?" @@ -41,9 +41,9 @@ def format_command_args(args): out = [] for arg in args: try: - try: + if isinstance(arg, (binary_type, text_type)): cmd = ensure_text(arg, errors="backslashreplace") - except TypeError: + else: cmd = stringify(arg) if len(cmd) > VALUE_MAX_LEN: diff --git a/ddtrace/internal/compat.py b/ddtrace/internal/compat.py index 70a34a0cec6..ee06690c601 100644 --- a/ddtrace/internal/compat.py +++ b/ddtrace/internal/compat.py @@ -51,6 +51,7 @@ ensure_str = six.ensure_str stringify = six.text_type string_type = six.string_types[0] +text_type = six.text_type binary_type = six.binary_type msgpack_type = six.binary_type # DEV: `six` doesn't have `float` in `integer_types` From cf2856d8798ec7e49228e565c7924fcbecc40830 Mon Sep 17 00:00:00 2001 From: Jameel Al-Aziz Date: Wed, 5 Jan 2022 14:26:47 -0800 Subject: [PATCH 6/9] Release notes --- .../fix-aioredis-unicodedecodeerror-b861b199e8757950.yaml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 releasenotes/notes/fix-aioredis-unicodedecodeerror-b861b199e8757950.yaml diff --git a/releasenotes/notes/fix-aioredis-unicodedecodeerror-b861b199e8757950.yaml b/releasenotes/notes/fix-aioredis-unicodedecodeerror-b861b199e8757950.yaml new file mode 100644 index 00000000000..bcb4ca01437 --- /dev/null +++ b/releasenotes/notes/fix-aioredis-unicodedecodeerror-b861b199e8757950.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Escape non-unicode bytes when decoding aioredis args. This fixes a + ``UnicodeDecodeError`` that can be thrown from the aioredis integration + when interacting with binary-encoded data, as is done in channels-redis. From bd884750be68b3a0b087394d240ec807b5a2d989 Mon Sep 17 00:00:00 2001 From: Jameel Al-Aziz Date: Wed, 5 Jan 2022 14:31:54 -0800 Subject: [PATCH 7/9] formatting --- ddtrace/contrib/redis/util.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ddtrace/contrib/redis/util.py b/ddtrace/contrib/redis/util.py index 1a608cfe13c..e0b041521f0 100644 --- a/ddtrace/contrib/redis/util.py +++ b/ddtrace/contrib/redis/util.py @@ -9,7 +9,10 @@ from ...ext import SpanTypes from ...ext import net from ...ext import redis as redisx -from ...internal.compat import binary_type, ensure_text, stringify, text_type +from ...internal.compat import binary_type +from ...internal.compat import ensure_text +from ...internal.compat import stringify +from ...internal.compat import text_type VALUE_PLACEHOLDER = "?" From e92b007632d2423e782aa8320893bcd8c1d2fbc4 Mon Sep 17 00:00:00 2001 From: Jameel Al-Aziz Date: Wed, 5 Jan 2022 15:06:22 -0800 Subject: [PATCH 8/9] revert list extend change --- ddtrace/contrib/aioredis/patch.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ddtrace/contrib/aioredis/patch.py b/ddtrace/contrib/aioredis/patch.py index 5d90663aa9c..c2752835c9f 100644 --- a/ddtrace/contrib/aioredis/patch.py +++ b/ddtrace/contrib/aioredis/patch.py @@ -155,7 +155,8 @@ async def traced_13_execute_pipeline(func, instance, args, kwargs): cmds = [] for _, cmd, cmd_args, _ in instance._pipeline: - parts = [cmd] + cmd_args + parts = [cmd] + parts.extend(cmd_args) cmds.append(format_command_args(parts)) resource = "\n".join(cmds) with pin.tracer.trace( From ca03b7730f560a8ce24d3af5b96a141084e9c6ac Mon Sep 17 00:00:00 2001 From: Jameel Al-Aziz Date: Wed, 5 Jan 2022 16:03:47 -0800 Subject: [PATCH 9/9] fix spelling --- .../notes/fix-aioredis-unicodedecodeerror-b861b199e8757950.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/notes/fix-aioredis-unicodedecodeerror-b861b199e8757950.yaml b/releasenotes/notes/fix-aioredis-unicodedecodeerror-b861b199e8757950.yaml index bcb4ca01437..235715fbcc4 100644 --- a/releasenotes/notes/fix-aioredis-unicodedecodeerror-b861b199e8757950.yaml +++ b/releasenotes/notes/fix-aioredis-unicodedecodeerror-b861b199e8757950.yaml @@ -1,6 +1,6 @@ --- fixes: - | - Escape non-unicode bytes when decoding aioredis args. This fixes a + Escape non-Unicode bytes when decoding aioredis args. This fixes a ``UnicodeDecodeError`` that can be thrown from the aioredis integration when interacting with binary-encoded data, as is done in channels-redis.