For what its worth, I can confirm it seems to relate to the custom encoder attempting to access a queryset whilst the lock is in place. I solved it by manually encoding the dict in a pre_save signal as a cheesy workaround.
@receiver(pre_save, sender=PipelineLog)
def set_psycopq_friendly_instance_context(sender, instance, raw, using, update_fields, **kwargs):
instance.context = json.loads(CustomJsonEncoder().encode(instance.context))