od(io) async(io)

Oggi #rant allucinante, è la prima volta che Python mi fa venire così tanta voglia di #piangere... mi farebbe proprio molto piacere conoscere chi sono i PAZZI pazzoidi che hanno completamente ROVINATO il linguaggio introducendo asyncio. Maremma trogolaia, è un merdaio allucinante secondo me, un'implementazione apparentemente furba di async/await, che però è estremamente malata: nel momento in cui si hanno davanti problemi reali, la presenza di anche un solo po' di codice asyncio va a contaminare tutto il resto che ne dipende, ed è un incubo... Sento già il "beh, se fa schifo questa funzione di Piton allora non usarla..."; peccato che tutte le librerie di terze parti che adottano questa maledizione per il loro funzionamento, puntualmente, vanno a deprecare il vecchio codice normale. Quindi si può solo piangere, concedetemelo. Fortunatamente, sembra che io non sia l'unica ad odiarlo... 😭️

Apparentemente, asyncio sembra appunto simile all'asincronicità che si ha su JavaScript da ES6, ma è molto, molto peggio. Il problema più grosso è che costringe alla propagazione, cioè: funzioni async possono essere chiamate solo usando await, ma la parola await può essere specificata solo dentro funzioni async... quindi, tutto un dato stack di chiamata deve essere fatto tenendo conto di asyncio, e questo significa dover ristrutturare per intero vecchi programmi (mentre, su JavaScript, funzioni asincrone possono essere richiamate anche senza attenderle, semplicemente omettendo await). Io il restructuring non la farò, perché non andrò a perdere ore solo per il risultato di rendere il mio codice ancora più complicato da mantenere, quindi... Per fortuna, esiste il metodo .create_task() per avviare qualcosa di asincrono da un contesto asincrono e lavarsene le mani... però, aspetta, esiste anche .ensure_future(), e... non se ne esce... 🥴️

Ecco un altro problema di asyncio che, coincidentalmente, fa anche rendere conto di quanto brutta di una hack sia la sua implementazione... è letteralmente costruita alla bene e meglio al di sopra di parti già esistenti di Python e che non avrebbero mai dovuto accomodare niente di simile. E, nel corso dei vari major update, questo modulo ha accumulato così tanta roba, teoricamente tutta diversa ma nella pratica indistinguibile per le differenze troppo sottili, che a questo punto non mi stupisco di come mai ho perso ore e ore appresso al mio problema oggi. Non ho letto il codice che ci sta dietro eh, mi risparmierò questa tortura, ma ho come il presentimento che sarà proprio la sagra del lasagna code... persino la documentazione ufficiale fa parecchio paura, e ciò fa pensare, perché in genere quella di Python mi pare tutto sommato buona — ho visto di meglio eh, però certamente non è malvagia. ⛏️

Addirittura asyncio avrebbe tecnicamente una specie di stato globale (bad!!!), al di sopra di cui è costruito un event loop. È per questo motivo che ha una serie spaventosa di metodi globali per gestirne il funzionamento (che non posso realisticamente elencare nella totalità), ed è questo che poi fa scaturire i problemi pratici nel mischiare codice asincrono con tradizionale. Soprattutto quando, come nel mio caso, il codice tradizionale gira molto attorno all'uso di thread secondari (perché deve lavorare in un modo che è sempre non bloccante, che non ha mai da awaitare niente). Vi giuro, ci ho provato in tutti i modi più strani letti da una decina di discussioni online diverse, ma no, non c'è verso di richiamare metodi gestiti da asyncio da un thread diverso da quello in cui questi risiedono (o partono errori, oppure addirittura comportamento indefinito). Su #Python non si può abusare il sistema degli eventi come su JavaScript per mettere una pezza a ciò, quindi la situazione è nera. 🕋️

Mi disp, dovevo sfogarmi, visto che tutto questo è stato semplicemente per... mettere le fondamenta per il bridging dei messaggi tra piattaforme diverse in #WinDog... nello specifico, verso Matrix (@windog:matrix.org), perché è con la libreria matrix-nio che sto sclerando. Maledetti loro che hanno fatto a pezzi il vecchio HttpClient, continuando a mantenere solo l'AsyncClient. Ancora non ho caricato il nuovo commit, ma comunque, questo è il codice che alla fine ho scritto per risolvere il problema... bruttissimo, non volevo davvero ricorrere a ciò, ma non voglio nemmeno ristrutturare l'intero bot, né tantomeno buttare questa libreria e reimplementare a manina in HTTP le tantissime API che mi servono. Quindi, la mia soluzione è stata usare una coda in cui inserire i dati che vengono da thread separati, e questa poi verrà gestita dall'unico thread dove gira il #codice di Matrix... tutto con un uso molto specifico dei vari metodi di asyncio. È dolore. 👇️

def handler(...):
  async def queue_handler():
    asyncio.ensure_future(queue_handler())
    if not len(queue):
      # evitare che la CPU vada al 100% ☠️
      time.sleep(0.01)
    while len(queue):
      sender(*queue.pop(0))
  async def client():
    # [...]
    asyncio.ensure_future(queue_handler())
    # [...]

def sender(...):
  try:
    asyncio.get_event_loop()
  except RuntimeError:
    queue.append(...)
    return None
  asyncio.create_task(...room_send(...))
@octospacc



  • Tags: async, asyncio, codice, piangere, problemi, programmazione, Python, rant, WinDog
  • Categories: Senza categoria