adm_autoreload.anubis 23.9 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644

                           The Anubis Project
                           
     A tool for an easy automatic loading/reloading of secondary modules.
        
        
   -------------------------------------------------------------------------
   *** (1) Overview. 
   *** (2) Carrying on. 
   *** (3) Automatic loading of servers. 
   *** (4) Organizing the loading of modules. 
   *** (5) Having a module loaded only temporarily.
   *** (6) Getting informations about loaded modules.  
   -------------------------------------------------------------------------


   *** (1) Overview. 
   
   This is a metaprogram generating an Anubis source file handling automatic
   loading/reloading of your secondary module (.adm files). Each module file is 
   loaded a first time and reloaded (within a parametrizable number of milliseconds) 
   each time the module file is updated (i.e. each time the adm file gets a more recent
   last modification time). 
   
   This means that while your program is running, you can recompile parts of your 
   program which will be automatically linked to the running program without stopping
   it. Everything will work fine because of the mechanisms implemented here (and if you
   follow some simple rules explained below). In particular, if the previous version 
   of a secondary module is currently in use, it will continue to execute conccurrently with
   the new one, until it has finished its job. 
   
   Note: the file names can be the names of symbolic links pointing to the actual 
   modules. 
   
   This metaprogram also manages families of modules. Such a family of modules is 
   identified by a file mask. All the modules in a family must have the same type. 
   
   Each adm loader has a name. In the explanations below we assume that the name of 
   the adm loader is 'bubu'. This name is used for constructing other names, namely
   type names and function names. 
   
   An adm file cannot contain non deterministic data such as network connections or
   dynamic variables. Nevertheless, when a module is loaded by 'load_adm' (defined
   in 'predefined.anubis'), the main program in the adm is executed. For example, 
   if the secondary adm is defined as: 
   
   global define MyAdm
      myadm 
        =
      <term>.

   <term> is executed while loading, and this execution can create dynamic variables
   and/or network connections or any other non deterministic item. We shall discuss
   this question in more details below in the section 'Automatic loading of servers', 
   because loading servers and properly starting and shutdowning automatically reloaded
   servers is somewhat delicate. 

   
          
   *** (2) Carrying on. 
          
   In order to use this metaprogram, you have to provide a list of metadata of type: 
   
public type SecondaryAdm:     // such a datum describes an adm (or a family of adms)
   adm
     (
       Int            delay,         // number of milliseconds that the loader waits in the loader loop
       String         directory,     // the path of the directory where to find the module(s)
       String         name,          // name (may be a mask) of the module(s) to be (re)loaded
       String         type           // type of the module(s) (in Anubis syntax)
     ). 
          
   If 'directory' is an absolute path, the modules(s) will be searched for in this path
   only. Otherwise, 'directory' is first assumed to be relative to the current working
   directory, and if not successful, it is assumed to be relative to the directory 
   'my_anubis/modules/'. 
          
   Create a file of your own, say 'my_modules.load', looking like this:
   
   
 
 --- my_modules.load ----------------------------------------------
    
 read tools/adm_autoreload.anubis
    
 global define One 
    make_my_adm_loader
      (
        List(String) args     // not used
      ) =
    make_adm_loader(      // this function is defined below
      "bubu",                                        // name of adm loader
      "generated/my_modules.load.anubis",            // path of the file to be generated
      ["something.anubis",...],                      // files that must be 'read' by the generated program
      [
        adm(1000, "...",  "*_gaga.adm",  "Gaga"),       // this is a family of modules of type 'Gaga'
        adm(2000, "...",  "toto.adm",    "Toto"),       // this is a single module of type 'Toto'
        ...
      ]).
    
 execute anbexec make_my_adm_loader            // generate the file 
 read generated/my_modules.load.anubis         // check that everything is ok
 
 --- end of my_modules.load ----------------------------------------
 
 
    
   'make_adm_loader' is declared as follows:
   
public define One
   make_adm_loader
     (
       String              loader_name, 
       String              target_file_path, 
       List(String)        to_be_read, 
       List(SecondaryAdm)  modules
     ). 
   
   Compiling 'my_modules.load' produce the file 'generated/my_modules.load.anubis' 
   that you can 'read' in other source files of your project. The public part in 
   this file contains: 
   
 public define List(Gaga)    __gaga_adm      (AdmLoader_bubu a). 
 public define Maybe(Toto)   toto_adm        (AdmLoader_bubu a). 
 ...
 
   These functions are non deterministic since they return the current 
   version of the content of secondary modules, which may change during
   execution of your main program. Hence, they must be called each time 
   you need these contents. 
   
   The above commands take an argument of type 'AdmLoader_bubu' that you can 
   obtain by 'starting the adm loader'. The starter function is declared like
   this in the generated file:  
   
 public define AdmLoader_bubu
    start_adm_loader_bubu
      (
        Var(Bool)                 shutdown_loader,
        String -> One             warn,
        Gaga -> One               start___gaga_adm, 
        Gaga -> One               stop___gaga_adm, 
        Toto -> One               start_toto_adm,
        Toto -> One               stop_toto_adm,
        ...
      ).
 
   The command 'start_adm_loader_bubu' creates and starts the adm loader named 
   'bubu'. The reason why the adm loader must have a name is that you can launch
   several adm loaders (which also have distinct types).  
   
   In general, you call the above function only once. The variable 'shutdown_loader'
   must contain 'false'. The adm loader (which is running in the backgound)
   is shutdown as soon as the value of this variable becomes 'true'. 
   
   The function 'warn' is called each time a loading problem occurs. Its argument
   is the error message. This function can print on the standard output, or to 
   a log file, or send an email to the administrator, ...
   
   The other arguments are callbacks which are used as follows by the loader. When
   a module is loaded the function 'stop_...' is applied to the previous version
   (if any) of the module. This allows for example, in the case
   the module contains a server, to shutdown the previous 
   version if there was one. The function 'start...' can start the new server. 
   The functions 'stop_...' are also used when the adm loader 
   itself is shutdown, i.e. when the variable 'shutdown_loader' receives the value 
   'true'. In case of a family of modules, the 'stop_...' function is applied to all
   the modules of the familly which are currently loaded. 
   
   Typically, these callbacks are used for starting and stopping servers, and in this
   case, the tool for retrieving the value of the module is likely not to be used. 
   
   
   
   *** (3) Automatic loading of servers. 
   
   As explained above, if a secondary adm contains a server, it is important to be able
   to start and shutdown such a server. This is the job of the 'start...' and 'stop...'
   functions. 
   
   Since version 1.13, the functions 'start_server' and 'start_ssl_server' defined in
   'predefined.anubis' have a new argument: 
   
      Var(Bool)    shutdown_required
      
   which can be used to shutdown the server. 
      
   In order to be able to properly start and shutdown servers, in particular when
   they are reloaded, and even if you have a family of servers (of the same type), 
   proceed as follows. In order to be better understood, we consider a real example. 
   
   Have a look at 'library/mail/smtp_server.anubis'. You will find the tool: 
   
   public define One
      start_smtp_server
        (
          SMTP_ServerDescription description, 
          Var(Bool)              shutdown
        ). 

   which allows to start a SMTP (outgoing mail) server from a formal description of 
   the server and a shutdown variable. In order to make a secondary adm for such a server, 
   write this:
   
   type LoadableSmtpServer: 
      smtp_serv(
          SMTP_ServerDescription description, 
          Var(Bool)              shutdown
               ). 
         
   and define your secondary module like this:       
               
   global define LoadableSmtpServer
      smtp_server_datum
        =
      with shutdown = var((Bool)false), 
      smtp_serv(my_server_description,
                shutdown). 
                
   When the secondary module is loaded, we get a datum of type LoadableSmtpServer. Then we
   can define the 'start' and 'stop' functions like this: 
   
   define One 
      start_smtp_srv
        (
          LoadableSmtpServer  s
        ) =
      if s is smtp_serv(desc,shutdown) then 
      start_smtp_server(desc,shutdown). 
      
   define One
      stop_smtp_srv
        (
          LoadableSmtpServer  s
        ) =
      if s is smtp_serv(desc,shutdown) then 
      shutdown <- true. 
      
   Notice that a new boolean variable is created at each loading so that each
   server has its own variable and executing any of the above two functions
   has an action only on this particular server.    
   
   If defined like this, these functions will work properly, even if you run
   a family of SMTP servers. In particular, garbage-collection will eliminate
   all stopped servers. For example, you may have three SMTP servers on ports
   8025, 8026 and 8027. The adm files should be named 'smtp_serv_8025.adm', 
   'smtp_serv_8026.adm' and 'smtp_serv_8027.adm', and the mask should be 
   'smtp_serv_*.adm'.  


   *** (4) Organizing the loading of modules. 
   
   Another important issue is the organization of adm loaders in a 'adm modularized'
   project. Here by 'adm modularized' we mean that the project is split into a main
   adm file and (many) secondary adm files. Doing like this has the advantage that 
   modifying the source of a module does not require to recompile the whole project
   (not even to stop the running programs). 
   
   However, the pitfall is that if two modules, say 'mod1' and 'mod2' are both using
   a third one, say 'mod_tools', they should not dynamically load 'mod_tools' independantly, 
   otherwise you will have two instances of 'mod_load' running in your program, which is 
   of course a waste of memory. 
   
   In this case, the module 'mod_tools' should better be loaded by the module 'mod0' whose
   job could be to load 'mod1' and 'mod2' and to transmit to them the content of 'mod_tools'. 
   This is like in some restaurants : you can either come with your own food or buy it in 
   the restaurant. Anyway, if your project is quite big, deciding who is loading who can be 
   tricky. 
   
   So, it appears that if 'mod1' is using 'mod_tools', you have two methods: 
   
     (1) mod1 loads mod_tools (and doesn't worry about other modules)
     (2) mod0 (or the primary module) loads mod_tools and transmits its
         content to mod1 after mod1 is loaded. 
         
   Case (1) is convenient if 'mod_tools' is a 'specialized' module (used only by
   mod1), and case (2) is convenient if 'mod_tools' is a 'general purpose' module. 
   
   Notice that a general purpose module is not the same thing as a source file. Indeed, 
   consider the case of 'tools/basis.anubis' which contains general purpose tools, and 
   which is quite big. It is not organized like a toolbox. It looks much more like
   a shop, where you can just peek what you need, no more. If your program has a 
   'read tools/basis.anubis' it does not contain all the stuff in 'basis.anubis'  
   because the compiler generates code only for what is actually used by your program. 
   Actually, this is even a little more complicated because the compiler generates
   a piece of code for each required instance of a schema. For example, in basis.anubis
   we have the schema:  
   
   public define Bool
      $T x /= $T y
         =
      ...
      
   If you use this in your program for comparing strings and for comparing integers, the 
   compiler will include two instances of this schema into your adm, one for comparing
   strings and one for comparing integers (they don't have the same code). 
   
   On the contrary, a secondary module contains only actual data (not schemas), and if 
   you wanted to include the above schema in a library, you would have to limit yourself
   to a sequences of instances for particular values of $T. 
   
   So, secondary modules and files like basis.anubis are clearly for distinct purposes. 
   Don't try to transform every file in your project into a secondary module. This will
   just not work. 
   
   Now, consider that we have an actual, fully justified, secondary module. If it is 
   a specialized module, just load it from the module using it. On the contrary, if it 
   is a general purpose module, you may want to load it when your program starts and to
   transmit its content to some modules to be loaded next. 
   
   However, there is a problem, because if you have two general purpose modules, it may be
   the case that one of them requires the content of the other one. You cannot load them
   in the same loader, because the loader loads the modules asynchronously. So, you must have 
   at least two loaders, one for loading a first bunch of modules, and another one for a
   second bunch of modules needing the contents of the modules of the first bunch. Of course, 
   it may be necessary to have more than two loaders. In order to put this method at work, 
   you probably need to draw a graph of dependancies of your secondary modules in order to 
   decide how many loaders you need. 
   
   Now, we come to the question of transmitting data from a module to another one. Assume that
   mod2 needs to have access to the content of mod1. After mod1 is loaded, its content is 
   available. Say it is of type T. This is the right moment for starting the second loader
   with a start... function containing the datum of type T within its microcontext. It's 
   clear that if you design the actual type of mod2 cleverly, you can transmit the content
   of mod1 to mod2 when this start function is executed (by the second loader). 
   
   All this needs a lot of organization, and we plan to propose a program for automating
   (at least partially) it in the future. 
   
   
   
   *** (5) Having a module loaded only temporarily.
   
   It may be the case that you have a quite big module which is seldom used. In this case, 
   you don't want to have it permanently into memory. You can proceed as follows. 
   
   Create a dedicated adm loader for this module, and don't start it when your program
   starts. On the contrary, start it only when needed. When the module should no more be used, 
   put the value 'true' in the 'shutdown_loader' variable of this adm loader. The loaded module
   will be collected together with the adm loader itself. Of course, you can restart this adm loader
   at any time.  
   
   
   
   *** (6) Getting informations about loaded modules.  
   
   During execution of your adm loader you can get informations about its current state. 
   Such a state is a list of data of type 'SecondaryAdmInfo' defined in the generated file. 
   In order to get this informations you can use the function 'get_info' of 
   type: 
   
        AdmLoader_bubu -> List(SecondaryAdmInfo)
        
   There is one item in the list for each currently loaded module, containing the directory, 
   name, mask, file name, loading time, ... 
    
  
   --- That's all for the public part ! --------------------------------
   
  
   

read tools/basis.anubis


   A tool for a prettier presentation of the generated text. 

define String
   pad_to
     (
       Int n, 
       String s
     ) =
   with l = length(s), 
   if n < l
   then s
   else s + constant_string(n-l,' '). 


   Constructing a symbol from a file mask. 

define List(Word8)
   strip_name
     (
       List(Word8) name
     ) =
   if name is 
   {
     [ ] then [ ], 
     [h . t] then 
       if member(h,"*.") 
       then ['_' . strip_name(t)] 
       else [h . strip_name(t)]
   }.

define String
   strip_name
     (
       String name
     ) =
   implode(strip_name(explode(name))). 


   Checking if a secondary module has a mask as its name:
   
define Bool   
   is_mask
     (
       SecondaryAdm a
     ) =
   member('*',name(a)). 


   Constructing the output type for the 'utilities'. 

define String
   out_type
     (
       SecondaryAdm a
     ) =
   (if is_mask(a) then "List" else "Maybe")+"("+type(a)+")". 


   Declaring and defining the 'utilities'. 

define SecondaryAdm -> String 
   utility_decl
     (
       String       loader_name
     ) =
   (SecondaryAdm a) |-> 
   "public define "+pad_to(35,out_type(a))+" "+
      pad_to(20,strip_name(name(a)))+"(AdmLoader_"+loader_name+" a).". 

define SecondaryAdm -> String 
   utility_def
     (
       String       loader_name
     ) =
   (SecondaryAdm a) |-> 
   with n = strip_name(name(a)),
   if is_mask(a)
   then "public define "+out_type(a)+" "+n+"(AdmLoader_"+loader_name+" a) =\n"+
        "   map(content,*instances(info_"+n+"(a))).\n" 
   else "public define "+out_type(a)+" "+n+"(AdmLoader_"+loader_name+" a) =\n"+
        "   if map(content,*instances(info_"+n+"(a))) is\n"+
        "   {\n"+
        "     [ ] then failure,\n"+
        "     [h . _] then success(h)\n"+
        "   }.\n\n".   


   Formatting a component of type 'AdmLoader_...':

define String
   make_component
     (
       SecondaryAdm a
     ) =
   "     AdmLoaderInfo("+type(a)+")  info_"+strip_name(name(a)). 

   
define String
   make_vars
     (
       List(SecondaryAdm) l
     ) =
   if l is 
   {
     [ ] then "", 
     [h . t] then if h is adm(delay,dir,name,type) then 
       with n = strip_name(name), 
       "   with "+n+"_v = var((List(AdmLoaderInstance("+type+")))[]),\n"
       + make_vars(t)
   }.          

define String
   make_info_items
     (
       List(SecondaryAdm) l
     ) =
   if l is 
   {
     [ ] then "", 
     [h . t] then if h is adm(delay,dir,name,type) then 
       with n = strip_name(name), 
       "   with info_"+n+" = adm_info(\""+dir+"\",\""+name+"\",\n"+
       "                              start_"+n+",\n"+
       "                              stop_"+n+",\n"+
       "                              "+n+"_v),\n"+
       make_info_items(t)
   }.

define String
   make_delegates
     (
       List(SecondaryAdm) l
     ) =
   if l is 
   {
     [ ] then "", 
     [h . t] then if h is adm(delay,dir,mask,type) then 
       with n = strip_name(mask), 
       "   delegate adm_reload_loop(adm_info(\""+directory(h)+"\",\n"+
       "                                     \""+mask+"\",\n"+
       "                                     start_"+n+",\n"+
       "                                     stop_"+n+",\n"+
       "                                     "+n+"_v),\n"+
       "                            shutdown_loader,\n"+
       "                            warn,\n"+
       "                            "+to_decimal(delay)+"),\n"+
       make_delegates(t)
   }.     



define String
   make_start_stop_decl
     (
       SecondaryAdm a
     ) =
   "     "+pad_to(50,type(a)+" -> One")+"  start_"+strip_name(name(a))+",\n"+  
   "     "+pad_to(50,type(a)+" -> One")+"  stop_"+strip_name(name(a)). 
   
define String
   make_to_info
     (
       SecondaryAdm  a
     ) =   
   if a is adm(delay,dir,mask,type) then 
   "   to_info(\""+abs_to_decimal(delay)+"\",\""+
                   dir+"\",\""+
                   mask+"\",\""+
                   type+"\")(info_"+strip_name(mask)+"(a))". 
   

public define One
   make_adm_loader
     (
       String                 loader_name, 
       String                 target_path,
       List(String)           to_be_read, 
       List(SecondaryAdm)     l
     ) =
   if file(target_path,new) is 
   {
     failure then print("Cannot create file "+target_path+"\n")
     success(t) then with target = (WStream)weaken(t), 
       print(target,
       
       "\n\n   This file was automatically generated.\n"+
       "   See 'library/tools/adm_autoreload.anubis' for explanations.\n\n"+
       
       "read tools/basis.anubis\n"+
       "read tools/ISO-8601.anubis\n"+
       "read tools/adm_autoreload_tools.anubis\n"+
       concat(map((String n) |-> "read "+n ,to_be_read),"\n")+"\n\n"+

       "public type AdmLoader_"+loader_name+":...   (an opaque type)\n\n"+
       "   'start_adm_loader_"+loader_name+"' creates and starts your adm loader.\n"+
       "   The dynamic variable 'shutdown_loader' must contain 'false'.\n"+
       "   The adm loader will shutdown as soon as this variable receives\n"+
       "   the value 'true'.\n\n"+
       
       "public define AdmLoader_"+loader_name+"    start_adm_loader_"+loader_name+"\n"+
       "   (\n"+
       "     Var(Bool)                                           shutdown_loader,\n"+
       "     String -> One                                       warn,\n"+
       concat(map(make_start_stop_decl,l),",\n")+
       "\n   ).\n\n"+ 
       
       
       
       "   Below are the utilities available in your adm loader.\n"+
       "   They always give you the latest version of your modules.\n"+
       "   Hence, you must not keep their return values, but call them each\n"+
       "   time you need their value.\n\n"+
       concat(map(utility_decl(loader_name),l),"\n")+
       "\n\n"); 
       
       
       print(target,
       "   Getting informations on currently loaded adms:\n\n"+
       "public type SecondaryAdmInfo:\n"+
       "   adm(\n"+
       "     String       directory,\n"+
       "     String       file_mask,\n"+
       "     String       file_name,\n"+
       "     String       loading_time,\n"+
       "     String       type,\n"+
       "     String       delay           // (milliseconds)\n"+
       "      ).\n\n"+
       "public define List(SecondaryAdmInfo)     get_info    (AdmLoader_"+loader_name+" a).\n\n"
       );
       
       print(target,
       "\n   --- That's all for the public part ! -------------------------\n\n"+
 
       //tools+

       "\n\npublic type AdmLoader_"+loader_name+":\n"+
       "   loader(\n"+
       concat(map(make_component,l),",\n")+
       "\n        ).\n\n"+
       
       //make_reload_loops(l)+
       
       "public define AdmLoader_"+loader_name+"    start_adm_loader_"+loader_name+"\n"+
       "   (\n"+
       "     Var(Bool)                                           shutdown_loader,\n"+
       "     String -> One                                       warn,\n"+
       concat(map(make_start_stop_decl,l),",\n")+
       "\n   ) =\n"+ 
       make_vars(l)+
       make_info_items(l)+
       make_delegates(l)+
       "   loader(\n"+concat(map((SecondaryAdm a) |-> 
           "      info_"+strip_name(name(a)),
                               l),",\n")+").\n\n"+
      
       concat(map(utility_def(loader_name),l),"\n")); 
       
       print(target,
       "define AdmLoaderInfo($T) -> List(SecondaryAdmInfo) to_info(\n"+
       "   String delay, String dir, String mask, String type) =\n"+
       "   (AdmLoaderInfo($T) a) |-> map((AdmLoaderInstance($T) i) |->\n"+
       "        if i is instance(fn,lt,_) then\n"+
       "        adm(dir,mask,fn,_Int_to_ISO_8601_date(to_Int(lt)),type,delay),\n"+
       "     *instances(a))."+
       "\n\n"); 
       
       print(target,
       "public define List(SecondaryAdmInfo)     get_info     (AdmLoader_"+loader_name+" a) =\n"+
       concat(map(make_to_info,l),"+\n")+
       ".\n\n"
       );

       print(target,
       "\n   --- end ---\n\n"
            )
   }.