Vai al contenuto principale

Il sistema operativo OpenVOS offre un'interfaccia di programmazione delle applicazioni (API) di alto livello che, nel complesso, semplifica la programmazione del sistema. Tuttavia, a volte diventa fin troppo semplice, poiché una semplice chiamata a una subroutine s$… potrebbe nascondere una notevole complessità.

Questo è il primo di una serie di post a cadenza irregolare volta a richiamare la vostra attenzione su tali insidie, affinché possiate evitarle nella progettazione delle vostre applicazioni.

I rischi dei messaggi della 25ª riga

La possibilità di scrivere un messaggio sulla25ª riga (o su quella che si trova più in basso) di un terminale consente a un utente (o a un programma in esecuzione) di avvisare gli altri utenti in caso di condizioni anomale. L'interfaccia a riga di comando è il comando `send_message`. Il comando e i programmi utente richiamano in ultima analisi la subroutine API `s$send_message`.    Gli argomenti di questa chiamata forniscono il nome_utente del destinatario, il nome_modulo di destinazione in cui l'utente potrebbe essere connesso, il testo della notifica e alcuni flag per modificare alcuni aspetti dell'operazione. I chiamanti con privilegi possono specificare il nome del mittente; in caso contrario, il valore predefinito è il nome_utente del processo mittente.

Fin qui sembra tutto chiaro. Poiché l'operazione richiede l'accesso al terminale di proprietà di un altro utente, il kernel invia la richiesta a una coda del server per il processo TheOverseer sul modulo di destinazione. Normalmente, il processo mittente attende quindi lo stato di consegna del messaggio, ma può scegliere di non attendere questa risposta.

Il processo TheOverseer di ogni modulo tiene traccia di tutti i processi di accesso e dei dispositivi terminali utilizzati da tali processi.   Quando riceve una richiesta di invio di un messaggio, esegue una ricerca nel proprio elenco di processi e dispositivi e, per ogni user_name del processo che corrisponde all'argomento receiver_name, associa una porta al dispositivo terminale del processo, apre la porta, scrive il messaggio sulla linea di notifica, chiude la porta e la disassocia. Se più processi corrispondono all'argomento receiver_name, questo ciclo di associazione/apertura/scrittura/chiusura/disassociazione viene ripetuto per ciascuno di essi.

Va bene, la questione è un po’ più complessa di quanto avessimo inizialmente previsto. Approfondendo l’argomento, scopriamo che qualsiasi modulo del sistema può ospitare processi di accesso in cui il terminale effettivo è un dispositivo su un altro sistema/modulo che ha utilizzato Open StrataLink (OSL) per effettuare l’accesso attraverso la rete (con il comando login –module).   Ora, l'operazione apparentemente semplice di attach/open/write/close/detach su quel terminale deve coinvolgere processi OSL e chiamate di procedura remota di rete al modulo remoto, ed è molto più onerosa rispetto alla gestione sul modulo stesso.

L'effetto si amplifica quando il nome utente destinatario è specificato come nome generico che potrebbe corrispondere a più utenti o a tutti gli utenti registrati. E si amplifica ulteriormente se anche il modulo di destinazione è specificato come nome generico. Il caso peggiore sarebbe l'invio del messaggio all'utente «*.*» (tutti gli utenti, tutti i gruppi) sul modulo «*» (tutti i moduli del sistema corrente).

Attuazione

s$expand_module_starname(target_module) =>  module_list;

foreach module_name in module_list
   send_overseer_request(module_name, send_message_request_data)
   The message is sent via a server queue and OSL to TheOverseer process
   on that module.

   TheOverseer  process on each receiving module then does:
   foreach terminal_process in TheOverseer’s list of
      login processes and sub-processes;

      if (terminal_process user_name matches receiver star name)
         if (messages_queued_for_device < 5)
            queue it for the login device
         else
            reject the request (e$too_many_terminal_messages)
   Contemporaneamente, utilizzare fino a 10 porte alla volta per inviare i messaggi in coda
        ai terminali;
   Per ogni messaggio per ciascun terminale
      Collegare la porta
      Aprire la porta
      s$control(…WRITE_SYSTEM_MESSAGE…)
      Chiudere la porta
      Scollegare la porta

Questa elaborazione viene eseguita in modalità no_wait_mode per ciascun terminale, in modo che i ritardi (come le richieste di I/O attraverso la rete) influenzino solo l'elaborazione dei messaggi per quel terminale.   
Se un messaggio non può essere consegnato entro 300 secondi, viene eliminato.

Quando un'applicazione utilizza questo meccanismo per segnalare gli errori e si verifica un'ondata di messaggi di errore, l'applicazione può facilmente rimanere bloccata nella gestione di tali segnalazioni, alternando momenti di attesa per l'elaborazione di altri messaggi a momenti in cui inonda i terminali degli utenti con messaggi che arrivano troppo velocemente per poter essere compresi.

Evitamento

Ora che sai cosa succede dietro le quinte, il consiglio che posso darti è:

  • Scegli con cura i nomi con asterisco per gli utenti e i moduli, in modo da assicurarti di raggiungere solo gli utenti a cui il messaggio è destinato ed esclusivamente loro. Ad esempio, John_Doe.* oppure *.Operations oppure John_Doe.SysAdmin.
  • Si consiglia di creare un elenco di utenti che non contenga nomi con asterisco, in modo da poter inviare ogni notifica separatamente.
  • Inserisci i dettagli dei messaggi di errore in un altro posto (ad esempio, un registro degli errori) e usa `s$send_message` per segnalare solo la presenza di errori.
  • Limita la frequenza delle notifiche a un livello ragionevole (magari non più di una volta al minuto).
  • Elimina qualsiasi messaggio identico all'ultimo inviato. Il nuovo messaggio andrebbe semplicemente a sovrapporsi a quello precedente.

Se avete altri suggerimenti, vi preghiamo di pubblicare i vostri commenti affinché possano leggerli anche gli altri.