/usr/lib/ruby/2.3.0/drb/drb.rb is in libruby2.3 2.3.0-5ubuntu1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
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 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 | # frozen_string_literal: false
#
# = drb/drb.rb
#
# Distributed Ruby: _dRuby_ version 2.0.4
#
# Copyright (c) 1999-2003 Masatoshi SEKI. You can redistribute it and/or
# modify it under the same terms as Ruby.
#
# Author:: Masatoshi SEKI
#
# Documentation:: William Webber (william@williamwebber.com)
#
# == Overview
#
# dRuby is a distributed object system for Ruby. It allows an object in one
# Ruby process to invoke methods on an object in another Ruby process on the
# same or a different machine.
#
# The Ruby standard library contains the core classes of the dRuby package.
# However, the full package also includes access control lists and the
# Rinda tuple-space distributed task management system, as well as a
# large number of samples. The full dRuby package can be downloaded from
# the dRuby home page (see *References*).
#
# For an introduction and examples of usage see the documentation to the
# DRb module.
#
# == References
#
# [http://www2a.biglobe.ne.jp/~seki/ruby/druby.html]
# The dRuby home page, in Japanese. Contains the full dRuby package
# and links to other Japanese-language sources.
#
# [http://www2a.biglobe.ne.jp/~seki/ruby/druby.en.html]
# The English version of the dRuby home page.
#
# [http://pragprog.com/book/sidruby/the-druby-book]
# The dRuby Book: Distributed and Parallel Computing with Ruby
# by Masatoshi Seki and Makoto Inoue
#
# [http://www.ruby-doc.org/docs/ProgrammingRuby/html/ospace.html]
# The chapter from *Programming* *Ruby* by Dave Thomas and Andy Hunt
# which discusses dRuby.
#
# [http://www.clio.ne.jp/home/web-i31s/Flotuard/Ruby/PRC2K_seki/dRuby.en.html]
# Translation of presentation on Ruby by Masatoshi Seki.
require 'socket'
require 'thread'
require 'io/wait'
require 'drb/eq'
#
# == Overview
#
# dRuby is a distributed object system for Ruby. It is written in
# pure Ruby and uses its own protocol. No add-in services are needed
# beyond those provided by the Ruby runtime, such as TCP sockets. It
# does not rely on or interoperate with other distributed object
# systems such as CORBA, RMI, or .NET.
#
# dRuby allows methods to be called in one Ruby process upon a Ruby
# object located in another Ruby process, even on another machine.
# References to objects can be passed between processes. Method
# arguments and return values are dumped and loaded in marshalled
# format. All of this is done transparently to both the caller of the
# remote method and the object that it is called upon.
#
# An object in a remote process is locally represented by a
# DRb::DRbObject instance. This acts as a sort of proxy for the
# remote object. Methods called upon this DRbObject instance are
# forwarded to its remote object. This is arranged dynamically at run
# time. There are no statically declared interfaces for remote
# objects, such as CORBA's IDL.
#
# dRuby calls made into a process are handled by a DRb::DRbServer
# instance within that process. This reconstitutes the method call,
# invokes it upon the specified local object, and returns the value to
# the remote caller. Any object can receive calls over dRuby. There
# is no need to implement a special interface, or mixin special
# functionality. Nor, in the general case, does an object need to
# explicitly register itself with a DRbServer in order to receive
# dRuby calls.
#
# One process wishing to make dRuby calls upon another process must
# somehow obtain an initial reference to an object in the remote
# process by some means other than as the return value of a remote
# method call, as there is initially no remote object reference it can
# invoke a method upon. This is done by attaching to the server by
# URI. Each DRbServer binds itself to a URI such as
# 'druby://example.com:8787'. A DRbServer can have an object attached
# to it that acts as the server's *front* *object*. A DRbObject can
# be explicitly created from the server's URI. This DRbObject's
# remote object will be the server's front object. This front object
# can then return references to other Ruby objects in the DRbServer's
# process.
#
# Method calls made over dRuby behave largely the same as normal Ruby
# method calls made within a process. Method calls with blocks are
# supported, as are raising exceptions. In addition to a method's
# standard errors, a dRuby call may also raise one of the
# dRuby-specific errors, all of which are subclasses of DRb::DRbError.
#
# Any type of object can be passed as an argument to a dRuby call or
# returned as its return value. By default, such objects are dumped
# or marshalled at the local end, then loaded or unmarshalled at the
# remote end. The remote end therefore receives a copy of the local
# object, not a distributed reference to it; methods invoked upon this
# copy are executed entirely in the remote process, not passed on to
# the local original. This has semantics similar to pass-by-value.
#
# However, if an object cannot be marshalled, a dRuby reference to it
# is passed or returned instead. This will turn up at the remote end
# as a DRbObject instance. All methods invoked upon this remote proxy
# are forwarded to the local object, as described in the discussion of
# DRbObjects. This has semantics similar to the normal Ruby
# pass-by-reference.
#
# The easiest way to signal that we want an otherwise marshallable
# object to be passed or returned as a DRbObject reference, rather
# than marshalled and sent as a copy, is to include the
# DRb::DRbUndumped mixin module.
#
# dRuby supports calling remote methods with blocks. As blocks (or
# rather the Proc objects that represent them) are not marshallable,
# the block executes in the local, not the remote, context. Each
# value yielded to the block is passed from the remote object to the
# local block, then the value returned by each block invocation is
# passed back to the remote execution context to be collected, before
# the collected values are finally returned to the local context as
# the return value of the method invocation.
#
# == Examples of usage
#
# For more dRuby samples, see the +samples+ directory in the full
# dRuby distribution.
#
# === dRuby in client/server mode
#
# This illustrates setting up a simple client-server drb
# system. Run the server and client code in different terminals,
# starting the server code first.
#
# ==== Server code
#
# require 'drb/drb'
#
# # The URI for the server to connect to
# URI="druby://localhost:8787"
#
# class TimeServer
#
# def get_current_time
# return Time.now
# end
#
# end
#
# # The object that handles requests on the server
# FRONT_OBJECT=TimeServer.new
#
# $SAFE = 1 # disable eval() and friends
#
# DRb.start_service(URI, FRONT_OBJECT)
# # Wait for the drb server thread to finish before exiting.
# DRb.thread.join
#
# ==== Client code
#
# require 'drb/drb'
#
# # The URI to connect to
# SERVER_URI="druby://localhost:8787"
#
# # Start a local DRbServer to handle callbacks.
# #
# # Not necessary for this small example, but will be required
# # as soon as we pass a non-marshallable object as an argument
# # to a dRuby call.
# #
# # Note: this must be called at least once per process to take any effect.
# # This is particularly important if your application forks.
# DRb.start_service
#
# timeserver = DRbObject.new_with_uri(SERVER_URI)
# puts timeserver.get_current_time
#
# === Remote objects under dRuby
#
# This example illustrates returning a reference to an object
# from a dRuby call. The Logger instances live in the server
# process. References to them are returned to the client process,
# where methods can be invoked upon them. These methods are
# executed in the server process.
#
# ==== Server code
#
# require 'drb/drb'
#
# URI="druby://localhost:8787"
#
# class Logger
#
# # Make dRuby send Logger instances as dRuby references,
# # not copies.
# include DRb::DRbUndumped
#
# def initialize(n, fname)
# @name = n
# @filename = fname
# end
#
# def log(message)
# File.open(@filename, "a") do |f|
# f.puts("#{Time.now}: #{@name}: #{message}")
# end
# end
#
# end
#
# # We have a central object for creating and retrieving loggers.
# # This retains a local reference to all loggers created. This
# # is so an existing logger can be looked up by name, but also
# # to prevent loggers from being garbage collected. A dRuby
# # reference to an object is not sufficient to prevent it being
# # garbage collected!
# class LoggerFactory
#
# def initialize(bdir)
# @basedir = bdir
# @loggers = {}
# end
#
# def get_logger(name)
# if !@loggers.has_key? name
# # make the filename safe, then declare it to be so
# fname = name.gsub(/[.\/\\\:]/, "_").untaint
# @loggers[name] = Logger.new(name, @basedir + "/" + fname)
# end
# return @loggers[name]
# end
#
# end
#
# FRONT_OBJECT=LoggerFactory.new("/tmp/dlog")
#
# $SAFE = 1 # disable eval() and friends
#
# DRb.start_service(URI, FRONT_OBJECT)
# DRb.thread.join
#
# ==== Client code
#
# require 'drb/drb'
#
# SERVER_URI="druby://localhost:8787"
#
# DRb.start_service
#
# log_service=DRbObject.new_with_uri(SERVER_URI)
#
# ["loga", "logb", "logc"].each do |logname|
#
# logger=log_service.get_logger(logname)
#
# logger.log("Hello, world!")
# logger.log("Goodbye, world!")
# logger.log("=== EOT ===")
#
# end
#
# == Security
#
# As with all network services, security needs to be considered when
# using dRuby. By allowing external access to a Ruby object, you are
# not only allowing outside clients to call the methods you have
# defined for that object, but by default to execute arbitrary Ruby
# code on your server. Consider the following:
#
# # !!! UNSAFE CODE !!!
# ro = DRbObject::new_with_uri("druby://your.server.com:8989")
# class << ro
# undef :instance_eval # force call to be passed to remote object
# end
# ro.instance_eval("`rm -rf *`")
#
# The dangers posed by instance_eval and friends are such that a
# DRbServer should generally be run with $SAFE set to at least
# level 1. This will disable eval() and related calls on strings
# passed across the wire. The sample usage code given above follows
# this practice.
#
# A DRbServer can be configured with an access control list to
# selectively allow or deny access from specified IP addresses. The
# main druby distribution provides the ACL class for this purpose. In
# general, this mechanism should only be used alongside, rather than
# as a replacement for, a good firewall.
#
# == dRuby internals
#
# dRuby is implemented using three main components: a remote method
# call marshaller/unmarshaller; a transport protocol; and an
# ID-to-object mapper. The latter two can be directly, and the first
# indirectly, replaced, in order to provide different behaviour and
# capabilities.
#
# Marshalling and unmarshalling of remote method calls is performed by
# a DRb::DRbMessage instance. This uses the Marshal module to dump
# the method call before sending it over the transport layer, then
# reconstitute it at the other end. There is normally no need to
# replace this component, and no direct way is provided to do so.
# However, it is possible to implement an alternative marshalling
# scheme as part of an implementation of the transport layer.
#
# The transport layer is responsible for opening client and server
# network connections and forwarding dRuby request across them.
# Normally, it uses DRb::DRbMessage internally to manage marshalling
# and unmarshalling. The transport layer is managed by
# DRb::DRbProtocol. Multiple protocols can be installed in
# DRbProtocol at the one time; selection between them is determined by
# the scheme of a dRuby URI. The default transport protocol is
# selected by the scheme 'druby:', and implemented by
# DRb::DRbTCPSocket. This uses plain TCP/IP sockets for
# communication. An alternative protocol, using UNIX domain sockets,
# is implemented by DRb::DRbUNIXSocket in the file drb/unix.rb, and
# selected by the scheme 'drbunix:'. A sample implementation over
# HTTP can be found in the samples accompanying the main dRuby
# distribution.
#
# The ID-to-object mapping component maps dRuby object ids to the
# objects they refer to, and vice versa. The implementation to use
# can be specified as part of a DRb::DRbServer's configuration. The
# default implementation is provided by DRb::DRbIdConv. It uses an
# object's ObjectSpace id as its dRuby id. This means that the dRuby
# reference to that object only remains meaningful for the lifetime of
# the object's process and the lifetime of the object within that
# process. A modified implementation is provided by DRb::TimerIdConv
# in the file drb/timeridconv.rb. This implementation retains a local
# reference to all objects exported over dRuby for a configurable
# period of time (defaulting to ten minutes), to prevent them being
# garbage-collected within this time. Another sample implementation
# is provided in sample/name.rb in the main dRuby distribution. This
# allows objects to specify their own id or "name". A dRuby reference
# can be made persistent across processes by having each process
# register an object using the same dRuby name.
#
module DRb
# Superclass of all errors raised in the DRb module.
class DRbError < RuntimeError; end
# Error raised when an error occurs on the underlying communication
# protocol.
class DRbConnError < DRbError; end
# Class responsible for converting between an object and its id.
#
# This, the default implementation, uses an object's local ObjectSpace
# __id__ as its id. This means that an object's identification over
# drb remains valid only while that object instance remains alive
# within the server runtime.
#
# For alternative mechanisms, see DRb::TimerIdConv in rdb/timeridconv.rb
# and DRbNameIdConv in sample/name.rb in the full drb distribution.
class DRbIdConv
# Convert an object reference id to an object.
#
# This implementation looks up the reference id in the local object
# space and returns the object it refers to.
def to_obj(ref)
ObjectSpace._id2ref(ref)
end
# Convert an object into a reference id.
#
# This implementation returns the object's __id__ in the local
# object space.
def to_id(obj)
obj.nil? ? nil : obj.__id__
end
end
# Mixin module making an object undumpable or unmarshallable.
#
# If an object which includes this module is returned by method
# called over drb, then the object remains in the server space
# and a reference to the object is returned, rather than the
# object being marshalled and moved into the client space.
module DRbUndumped
def _dump(dummy) # :nodoc:
raise TypeError, 'can\'t dump'
end
end
# Error raised by the DRb module when an attempt is made to refer to
# the context's current drb server but the context does not have one.
# See #current_server.
class DRbServerNotFound < DRbError; end
# Error raised by the DRbProtocol module when it cannot find any
# protocol implementation support the scheme specified in a URI.
class DRbBadURI < DRbError; end
# Error raised by a dRuby protocol when it doesn't support the
# scheme specified in a URI. See DRb::DRbProtocol.
class DRbBadScheme < DRbError; end
# An exception wrapping a DRb::DRbUnknown object
class DRbUnknownError < DRbError
# Create a new DRbUnknownError for the DRb::DRbUnknown object +unknown+
def initialize(unknown)
@unknown = unknown
super(unknown.name)
end
# Get the wrapped DRb::DRbUnknown object.
attr_reader :unknown
def self._load(s) # :nodoc:
Marshal::load(s)
end
def _dump(lv) # :nodoc:
Marshal::dump(@unknown)
end
end
# An exception wrapping an error object
class DRbRemoteError < DRbError
# Creates a new remote error that wraps the Exception +error+
def initialize(error)
@reason = error.class.to_s
super("#{error.message} (#{error.class})")
set_backtrace(error.backtrace)
end
# the class of the error, as a string.
attr_reader :reason
end
# Class wrapping a marshalled object whose type is unknown locally.
#
# If an object is returned by a method invoked over drb, but the
# class of the object is unknown in the client namespace, or
# the object is a constant unknown in the client namespace, then
# the still-marshalled object is returned wrapped in a DRbUnknown instance.
#
# If this object is passed as an argument to a method invoked over
# drb, then the wrapped object is passed instead.
#
# The class or constant name of the object can be read from the
# +name+ attribute. The marshalled object is held in the +buf+
# attribute.
class DRbUnknown
# Create a new DRbUnknown object.
#
# +buf+ is a string containing a marshalled object that could not
# be unmarshalled. +err+ is the error message that was raised
# when the unmarshalling failed. It is used to determine the
# name of the unmarshalled object.
def initialize(err, buf)
case err.to_s
when /uninitialized constant (\S+)/
@name = $1
when /undefined class\/module (\S+)/
@name = $1
else
@name = nil
end
@buf = buf
end
# The name of the unknown thing.
#
# Class name for unknown objects; variable name for unknown
# constants.
attr_reader :name
# Buffer contained the marshalled, unknown object.
attr_reader :buf
def self._load(s) # :nodoc:
begin
Marshal::load(s)
rescue NameError, ArgumentError
DRbUnknown.new($!, s)
end
end
def _dump(lv) # :nodoc:
@buf
end
# Attempt to load the wrapped marshalled object again.
#
# If the class of the object is now known locally, the object
# will be unmarshalled and returned. Otherwise, a new
# but identical DRbUnknown object will be returned.
def reload
self.class._load(@buf)
end
# Create a DRbUnknownError exception containing this object.
def exception
DRbUnknownError.new(self)
end
end
# An Array wrapper that can be sent to another server via DRb.
#
# All entries in the array will be dumped or be references that point to
# the local server.
class DRbArray
# Creates a new DRbArray that either dumps or wraps all the items in the
# Array +ary+ so they can be loaded by a remote DRb server.
def initialize(ary)
@ary = ary.collect { |obj|
if obj.kind_of? DRbUndumped
DRbObject.new(obj)
else
begin
Marshal.dump(obj)
obj
rescue
DRbObject.new(obj)
end
end
}
end
def self._load(s) # :nodoc:
Marshal::load(s)
end
def _dump(lv) # :nodoc:
Marshal.dump(@ary)
end
end
# Handler for sending and receiving drb messages.
#
# This takes care of the low-level marshalling and unmarshalling
# of drb requests and responses sent over the wire between server
# and client. This relieves the implementor of a new drb
# protocol layer with having to deal with these details.
#
# The user does not have to directly deal with this object in
# normal use.
class DRbMessage
def initialize(config) # :nodoc:
@load_limit = config[:load_limit]
@argc_limit = config[:argc_limit]
end
def dump(obj, error=false) # :nodoc:
obj = make_proxy(obj, error) if obj.kind_of? DRbUndumped
begin
str = Marshal::dump(obj)
rescue
str = Marshal::dump(make_proxy(obj, error))
end
[str.size].pack('N') + str
end
def load(soc) # :nodoc:
begin
sz = soc.read(4) # sizeof (N)
rescue
raise(DRbConnError, $!.message, $!.backtrace)
end
raise(DRbConnError, 'connection closed') if sz.nil?
raise(DRbConnError, 'premature header') if sz.size < 4
sz = sz.unpack('N')[0]
raise(DRbConnError, "too large packet #{sz}") if @load_limit < sz
begin
str = soc.read(sz)
rescue
raise(DRbConnError, $!.message, $!.backtrace)
end
raise(DRbConnError, 'connection closed') if str.nil?
raise(DRbConnError, 'premature marshal format(can\'t read)') if str.size < sz
DRb.mutex.synchronize do
begin
save = Thread.current[:drb_untaint]
Thread.current[:drb_untaint] = []
Marshal::load(str)
rescue NameError, ArgumentError
DRbUnknown.new($!, str)
ensure
Thread.current[:drb_untaint].each do |x|
x.untaint
end
Thread.current[:drb_untaint] = save
end
end
end
def send_request(stream, ref, msg_id, arg, b) # :nodoc:
ary = []
ary.push(dump(ref.__drbref))
ary.push(dump(msg_id.id2name))
ary.push(dump(arg.length))
arg.each do |e|
ary.push(dump(e))
end
ary.push(dump(b))
stream.write(ary.join(''))
rescue
raise(DRbConnError, $!.message, $!.backtrace)
end
def recv_request(stream) # :nodoc:
ref = load(stream)
ro = DRb.to_obj(ref)
msg = load(stream)
argc = load(stream)
raise(DRbConnError, "too many arguments") if @argc_limit < argc
argv = Array.new(argc, nil)
argc.times do |n|
argv[n] = load(stream)
end
block = load(stream)
return ro, msg, argv, block
end
def send_reply(stream, succ, result) # :nodoc:
stream.write(dump(succ) + dump(result, !succ))
rescue
raise(DRbConnError, $!.message, $!.backtrace)
end
def recv_reply(stream) # :nodoc:
succ = load(stream)
result = load(stream)
[succ, result]
end
private
def make_proxy(obj, error=false) # :nodoc:
if error
DRbRemoteError.new(obj)
else
DRbObject.new(obj)
end
end
end
# Module managing the underlying network protocol(s) used by drb.
#
# By default, drb uses the DRbTCPSocket protocol. Other protocols
# can be defined. A protocol must define the following class methods:
#
# [open(uri, config)] Open a client connection to the server at +uri+,
# using configuration +config+. Return a protocol
# instance for this connection.
# [open_server(uri, config)] Open a server listening at +uri+,
# using configuration +config+. Return a
# protocol instance for this listener.
# [uri_option(uri, config)] Take a URI, possibly containing an option
# component (e.g. a trailing '?param=val'),
# and return a [uri, option] tuple.
#
# All of these methods should raise a DRbBadScheme error if the URI
# does not identify the protocol they support (e.g. "druby:" for
# the standard Ruby protocol). This is how the DRbProtocol module,
# given a URI, determines which protocol implementation serves that
# protocol.
#
# The protocol instance returned by #open_server must have the
# following methods:
#
# [accept] Accept a new connection to the server. Returns a protocol
# instance capable of communicating with the client.
# [close] Close the server connection.
# [uri] Get the URI for this server.
#
# The protocol instance returned by #open must have the following methods:
#
# [send_request (ref, msg_id, arg, b)]
# Send a request to +ref+ with the given message id and arguments.
# This is most easily implemented by calling DRbMessage.send_request,
# providing a stream that sits on top of the current protocol.
# [recv_reply]
# Receive a reply from the server and return it as a [success-boolean,
# reply-value] pair. This is most easily implemented by calling
# DRb.recv_reply, providing a stream that sits on top of the
# current protocol.
# [alive?]
# Is this connection still alive?
# [close]
# Close this connection.
#
# The protocol instance returned by #open_server().accept() must have
# the following methods:
#
# [recv_request]
# Receive a request from the client and return a [object, message,
# args, block] tuple. This is most easily implemented by calling
# DRbMessage.recv_request, providing a stream that sits on top of
# the current protocol.
# [send_reply(succ, result)]
# Send a reply to the client. This is most easily implemented
# by calling DRbMessage.send_reply, providing a stream that sits
# on top of the current protocol.
# [close]
# Close this connection.
#
# A new protocol is registered with the DRbProtocol module using
# the add_protocol method.
#
# For examples of other protocols, see DRbUNIXSocket in drb/unix.rb,
# and HTTP0 in sample/http0.rb and sample/http0serv.rb in the full
# drb distribution.
module DRbProtocol
# Add a new protocol to the DRbProtocol module.
def add_protocol(prot)
@protocol.push(prot)
end
module_function :add_protocol
# Open a client connection to +uri+ with the configuration +config+.
#
# The DRbProtocol module asks each registered protocol in turn to
# try to open the URI. Each protocol signals that it does not handle that
# URI by raising a DRbBadScheme error. If no protocol recognises the
# URI, then a DRbBadURI error is raised. If a protocol accepts the
# URI, but an error occurs in opening it, a DRbConnError is raised.
def open(uri, config, first=true)
@protocol.each do |prot|
begin
return prot.open(uri, config)
rescue DRbBadScheme
rescue DRbConnError
raise($!)
rescue
raise(DRbConnError, "#{uri} - #{$!.inspect}")
end
end
if first && (config[:auto_load] != false)
auto_load(uri)
return open(uri, config, false)
end
raise DRbBadURI, 'can\'t parse uri:' + uri
end
module_function :open
# Open a server listening for connections at +uri+ with
# configuration +config+.
#
# The DRbProtocol module asks each registered protocol in turn to
# try to open a server at the URI. Each protocol signals that it does
# not handle that URI by raising a DRbBadScheme error. If no protocol
# recognises the URI, then a DRbBadURI error is raised. If a protocol
# accepts the URI, but an error occurs in opening it, the underlying
# error is passed on to the caller.
def open_server(uri, config, first=true)
@protocol.each do |prot|
begin
return prot.open_server(uri, config)
rescue DRbBadScheme
end
end
if first && (config[:auto_load] != false)
auto_load(uri)
return open_server(uri, config, false)
end
raise DRbBadURI, 'can\'t parse uri:' + uri
end
module_function :open_server
# Parse +uri+ into a [uri, option] pair.
#
# The DRbProtocol module asks each registered protocol in turn to
# try to parse the URI. Each protocol signals that it does not handle that
# URI by raising a DRbBadScheme error. If no protocol recognises the
# URI, then a DRbBadURI error is raised.
def uri_option(uri, config, first=true)
@protocol.each do |prot|
begin
uri, opt = prot.uri_option(uri, config)
# opt = nil if opt == ''
return uri, opt
rescue DRbBadScheme
end
end
if first && (config[:auto_load] != false)
auto_load(uri)
return uri_option(uri, config, false)
end
raise DRbBadURI, 'can\'t parse uri:' + uri
end
module_function :uri_option
def auto_load(uri) # :nodoc:
if uri =~ /^drb([a-z0-9]+):/
require("drb/#{$1}") rescue nil
end
end
module_function :auto_load
end
# The default drb protocol which communicates over a TCP socket.
#
# The DRb TCP protocol URI looks like:
# <code>druby://<host>:<port>?<option></code>. The option is optional.
class DRbTCPSocket
# :stopdoc:
private
def self.parse_uri(uri)
if uri =~ /^druby:\/\/(.*?):(\d+)(\?(.*))?$/
host = $1
port = $2.to_i
option = $4
[host, port, option]
else
raise(DRbBadScheme, uri) unless uri =~ /^druby:/
raise(DRbBadURI, 'can\'t parse uri:' + uri)
end
end
public
# Open a client connection to +uri+ (DRb URI string) using configuration
# +config+.
#
# This can raise DRb::DRbBadScheme or DRb::DRbBadURI if +uri+ is not for a
# recognized protocol. See DRb::DRbServer.new for information on built-in
# URI protocols.
def self.open(uri, config)
host, port, = parse_uri(uri)
host.untaint
port.untaint
soc = TCPSocket.open(host, port)
self.new(uri, soc, config)
end
# Returns the hostname of this server
def self.getservername
host = Socket::gethostname
begin
Socket::gethostbyname(host)[0]
rescue
'localhost'
end
end
# For the families available for +host+, returns a TCPServer on +port+.
# If +port+ is 0 the first available port is used. IPv4 servers are
# preferred over IPv6 servers.
def self.open_server_inaddr_any(host, port)
infos = Socket::getaddrinfo(host, nil,
Socket::AF_UNSPEC,
Socket::SOCK_STREAM,
0,
Socket::AI_PASSIVE)
families = Hash[*infos.collect { |af, *_| af }.uniq.zip([]).flatten]
return TCPServer.open('0.0.0.0', port) if families.has_key?('AF_INET')
return TCPServer.open('::', port) if families.has_key?('AF_INET6')
return TCPServer.open(port)
# :stopdoc:
end
# Open a server listening for connections at +uri+ using
# configuration +config+.
def self.open_server(uri, config)
uri = 'druby://:0' unless uri
host, port, _ = parse_uri(uri)
config = {:tcp_original_host => host}.update(config)
if host.size == 0
host = getservername
soc = open_server_inaddr_any(host, port)
else
soc = TCPServer.open(host, port)
end
port = soc.addr[1] if port == 0
config[:tcp_port] = port
uri = "druby://#{host}:#{port}"
self.new(uri, soc, config)
end
# Parse +uri+ into a [uri, option] pair.
def self.uri_option(uri, config)
host, port, option = parse_uri(uri)
return "druby://#{host}:#{port}", option
end
# Create a new DRbTCPSocket instance.
#
# +uri+ is the URI we are connected to.
# +soc+ is the tcp socket we are bound to. +config+ is our
# configuration.
def initialize(uri, soc, config={})
@uri = uri
@socket = soc
@config = config
@acl = config[:tcp_acl]
@msg = DRbMessage.new(config)
set_sockopt(@socket)
@shutdown_pipe_r, @shutdown_pipe_w = IO.pipe
end
# Get the URI that we are connected to.
attr_reader :uri
# Get the address of our TCP peer (the other end of the socket
# we are bound to.
def peeraddr
@socket.peeraddr
end
# Get the socket.
def stream; @socket; end
# On the client side, send a request to the server.
def send_request(ref, msg_id, arg, b)
@msg.send_request(stream, ref, msg_id, arg, b)
end
# On the server side, receive a request from the client.
def recv_request
@msg.recv_request(stream)
end
# On the server side, send a reply to the client.
def send_reply(succ, result)
@msg.send_reply(stream, succ, result)
end
# On the client side, receive a reply from the server.
def recv_reply
@msg.recv_reply(stream)
end
public
# Close the connection.
#
# If this is an instance returned by #open_server, then this stops
# listening for new connections altogether. If this is an instance
# returned by #open or by #accept, then it closes this particular
# client-server session.
def close
if @socket
@socket.close
@socket = nil
end
close_shutdown_pipe
end
def close_shutdown_pipe
if @shutdown_pipe_r && !@shutdown_pipe_r.closed?
@shutdown_pipe_r.close
@shutdown_pipe_r = nil
end
if @shutdown_pipe_w && !@shutdown_pipe_w.closed?
@shutdown_pipe_w.close
@shutdown_pipe_w = nil
end
end
private :close_shutdown_pipe
# On the server side, for an instance returned by #open_server,
# accept a client connection and return a new instance to handle
# the server's side of this client-server session.
def accept
while true
s = accept_or_shutdown
return nil unless s
break if (@acl ? @acl.allow_socket?(s) : true)
s.close
end
if @config[:tcp_original_host].to_s.size == 0
uri = "druby://#{s.addr[3]}:#{@config[:tcp_port]}"
else
uri = @uri
end
self.class.new(uri, s, @config)
end
def accept_or_shutdown
readables, = IO.select([@socket, @shutdown_pipe_r])
if readables.include? @shutdown_pipe_r
return nil
end
@socket.accept
end
private :accept_or_shutdown
# Graceful shutdown
def shutdown
@shutdown_pipe_w.close if @shutdown_pipe_w && !@shutdown_pipe_w.closed?
end
# Check to see if this connection is alive.
def alive?
return false unless @socket
if @socket.to_io.wait_readable(0)
close
return false
end
true
end
def set_sockopt(soc) # :nodoc:
soc.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
end
end
module DRbProtocol
@protocol = [DRbTCPSocket] # default
end
class DRbURIOption # :nodoc: I don't understand the purpose of this class...
def initialize(option)
@option = option.to_s
end
attr_reader :option
def to_s; @option; end
def ==(other)
return false unless DRbURIOption === other
@option == other.option
end
def hash
@option.hash
end
alias eql? ==
end
# Object wrapping a reference to a remote drb object.
#
# Method calls on this object are relayed to the remote
# object that this object is a stub for.
class DRbObject
# Unmarshall a marshalled DRbObject.
#
# If the referenced object is located within the local server, then
# the object itself is returned. Otherwise, a new DRbObject is
# created to act as a stub for the remote referenced object.
def self._load(s)
uri, ref = Marshal.load(s)
if DRb.here?(uri)
obj = DRb.to_obj(ref)
if ((! obj.tainted?) && Thread.current[:drb_untaint])
Thread.current[:drb_untaint].push(obj)
end
return obj
end
self.new_with(uri, ref)
end
# Creates a DRb::DRbObject given the reference information to the remote
# host +uri+ and object +ref+.
def self.new_with(uri, ref)
it = self.allocate
it.instance_variable_set(:@uri, uri)
it.instance_variable_set(:@ref, ref)
it
end
# Create a new DRbObject from a URI alone.
def self.new_with_uri(uri)
self.new(nil, uri)
end
# Marshall this object.
#
# The URI and ref of the object are marshalled.
def _dump(lv)
Marshal.dump([@uri, @ref])
end
# Create a new remote object stub.
#
# +obj+ is the (local) object we want to create a stub for. Normally
# this is +nil+. +uri+ is the URI of the remote object that this
# will be a stub for.
def initialize(obj, uri=nil)
@uri = nil
@ref = nil
if obj.nil?
return if uri.nil?
@uri, option = DRbProtocol.uri_option(uri, DRb.config)
@ref = DRbURIOption.new(option) unless option.nil?
else
@uri = uri ? uri : (DRb.uri rescue nil)
@ref = obj ? DRb.to_id(obj) : nil
end
end
# Get the URI of the remote object.
def __drburi
@uri
end
# Get the reference of the object, if local.
def __drbref
@ref
end
undef :to_s
undef :to_a if respond_to?(:to_a)
# Routes respond_to? to the referenced remote object.
def respond_to?(msg_id, priv=false)
case msg_id
when :_dump
true
when :marshal_dump
false
else
method_missing(:respond_to?, msg_id, priv)
end
end
# Routes method calls to the referenced remote object.
def method_missing(msg_id, *a, &b)
if DRb.here?(@uri)
obj = DRb.to_obj(@ref)
DRb.current_server.check_insecure_method(obj, msg_id)
return obj.__send__(msg_id, *a, &b)
end
succ, result = self.class.with_friend(@uri) do
DRbConn.open(@uri) do |conn|
conn.send_message(self, msg_id, a, b)
end
end
if succ
return result
elsif DRbUnknown === result
raise result
else
bt = self.class.prepare_backtrace(@uri, result)
result.set_backtrace(bt + caller)
raise result
end
end
# Given the +uri+ of another host executes the block provided.
def self.with_friend(uri) # :nodoc:
friend = DRb.fetch_server(uri)
return yield() unless friend
save = Thread.current['DRb']
Thread.current['DRb'] = { 'server' => friend }
return yield
ensure
Thread.current['DRb'] = save if friend
end
# Returns a modified backtrace from +result+ with the +uri+ where each call
# in the backtrace came from.
def self.prepare_backtrace(uri, result) # :nodoc:
prefix = "(#{uri}) "
bt = []
result.backtrace.each do |x|
break if /`__send__'$/ =~ x
if /^\(druby:\/\// =~ x
bt.push(x)
else
bt.push(prefix + x)
end
end
bt
end
def pretty_print(q) # :nodoc:
q.pp_object(self)
end
def pretty_print_cycle(q) # :nodoc:
q.object_address_group(self) {
q.breakable
q.text '...'
}
end
end
# Class handling the connection between a DRbObject and the
# server the real object lives on.
#
# This class maintains a pool of connections, to reduce the
# overhead of starting and closing down connections for each
# method call.
#
# This class is used internally by DRbObject. The user does
# not normally need to deal with it directly.
class DRbConn
POOL_SIZE = 16 # :nodoc:
@mutex = Mutex.new
@pool = []
def self.open(remote_uri) # :nodoc:
begin
conn = nil
@mutex.synchronize do
#FIXME
new_pool = []
@pool.each do |c|
if conn.nil? and c.uri == remote_uri
conn = c if c.alive?
else
new_pool.push c
end
end
@pool = new_pool
end
conn = self.new(remote_uri) unless conn
succ, result = yield(conn)
return succ, result
ensure
if conn
if succ
@mutex.synchronize do
@pool.unshift(conn)
@pool.pop.close while @pool.size > POOL_SIZE
end
else
conn.close
end
end
end
end
def initialize(remote_uri) # :nodoc:
@uri = remote_uri
@protocol = DRbProtocol.open(remote_uri, DRb.config)
end
attr_reader :uri # :nodoc:
def send_message(ref, msg_id, arg, block) # :nodoc:
@protocol.send_request(ref, msg_id, arg, block)
@protocol.recv_reply
end
def close # :nodoc:
@protocol.close
@protocol = nil
end
def alive? # :nodoc:
return false unless @protocol
@protocol.alive?
end
end
# Class representing a drb server instance.
#
# A DRbServer must be running in the local process before any incoming
# dRuby calls can be accepted, or any local objects can be passed as
# dRuby references to remote processes, even if those local objects are
# never actually called remotely. You do not need to start a DRbServer
# in the local process if you are only making outgoing dRuby calls
# passing marshalled parameters.
#
# Unless multiple servers are being used, the local DRbServer is normally
# started by calling DRb.start_service.
class DRbServer
@@acl = nil
@@idconv = DRbIdConv.new
@@secondary_server = nil
@@argc_limit = 256
@@load_limit = 256 * 102400
@@verbose = false
@@safe_level = 0
# Set the default value for the :argc_limit option.
#
# See #new(). The initial default value is 256.
def self.default_argc_limit(argc)
@@argc_limit = argc
end
# Set the default value for the :load_limit option.
#
# See #new(). The initial default value is 25 MB.
def self.default_load_limit(sz)
@@load_limit = sz
end
# Set the default access control list to +acl+. The default ACL is +nil+.
#
# See also DRb::ACL and #new()
def self.default_acl(acl)
@@acl = acl
end
# Set the default value for the :id_conv option.
#
# See #new(). The initial default value is a DRbIdConv instance.
def self.default_id_conv(idconv)
@@idconv = idconv
end
# Set the default safe level to +level+. The default safe level is 0
#
# See #new for more information.
def self.default_safe_level(level)
@@safe_level = level
end
# Set the default value of the :verbose option.
#
# See #new(). The initial default value is false.
def self.verbose=(on)
@@verbose = on
end
# Get the default value of the :verbose option.
def self.verbose
@@verbose
end
def self.make_config(hash={}) # :nodoc:
default_config = {
:idconv => @@idconv,
:verbose => @@verbose,
:tcp_acl => @@acl,
:load_limit => @@load_limit,
:argc_limit => @@argc_limit,
:safe_level => @@safe_level
}
default_config.update(hash)
end
# Create a new DRbServer instance.
#
# +uri+ is the URI to bind to. This is normally of the form
# 'druby://<hostname>:<port>' where <hostname> is a hostname of
# the local machine. If nil, then the system's default hostname
# will be bound to, on a port selected by the system; these value
# can be retrieved from the +uri+ attribute. 'druby:' specifies
# the default dRuby transport protocol: another protocol, such
# as 'drbunix:', can be specified instead.
#
# +front+ is the front object for the server, that is, the object
# to which remote method calls on the server will be passed. If
# nil, then the server will not accept remote method calls.
#
# If +config_or_acl+ is a hash, it is the configuration to
# use for this server. The following options are recognised:
#
# :idconv :: an id-to-object conversion object. This defaults
# to an instance of the class DRb::DRbIdConv.
# :verbose :: if true, all unsuccessful remote calls on objects
# in the server will be logged to $stdout. false
# by default.
# :tcp_acl :: the access control list for this server. See
# the ACL class from the main dRuby distribution.
# :load_limit :: the maximum message size in bytes accepted by
# the server. Defaults to 25 MB (26214400).
# :argc_limit :: the maximum number of arguments to a remote
# method accepted by the server. Defaults to
# 256.
# :safe_level :: The safe level of the DRbServer. The attribute
# sets $SAFE for methods performed in the main_loop.
# Defaults to 0.
#
# The default values of these options can be modified on
# a class-wide basis by the class methods #default_argc_limit,
# #default_load_limit, #default_acl, #default_id_conv,
# and #verbose=
#
# If +config_or_acl+ is not a hash, but is not nil, it is
# assumed to be the access control list for this server.
# See the :tcp_acl option for more details.
#
# If no other server is currently set as the primary server,
# this will become the primary server.
#
# The server will immediately start running in its own thread.
def initialize(uri=nil, front=nil, config_or_acl=nil)
if Hash === config_or_acl
config = config_or_acl.dup
else
acl = config_or_acl || @@acl
config = {
:tcp_acl => acl
}
end
@config = self.class.make_config(config)
@protocol = DRbProtocol.open_server(uri, @config)
@uri = @protocol.uri
@exported_uri = [@uri]
@front = front
@idconv = @config[:idconv]
@safe_level = @config[:safe_level]
@grp = ThreadGroup.new
@thread = run
DRb.regist_server(self)
end
# The URI of this DRbServer.
attr_reader :uri
# The main thread of this DRbServer.
#
# This is the thread that listens for and accepts connections
# from clients, not that handles each client's request-response
# session.
attr_reader :thread
# The front object of the DRbServer.
#
# This object receives remote method calls made on the server's
# URI alone, with an object id.
attr_reader :front
# The configuration of this DRbServer
attr_reader :config
# The safe level for this server. This is a number corresponding to
# $SAFE.
#
# The default safe_level is 0
attr_reader :safe_level
# Set whether to operate in verbose mode.
#
# In verbose mode, failed calls are logged to stdout.
def verbose=(v); @config[:verbose]=v; end
# Get whether the server is in verbose mode.
#
# In verbose mode, failed calls are logged to stdout.
def verbose; @config[:verbose]; end
# Is this server alive?
def alive?
@thread.alive?
end
# Is +uri+ the URI for this server?
def here?(uri)
@exported_uri.include?(uri)
end
# Stop this server.
def stop_service
DRb.remove_server(self)
if Thread.current['DRb'] && Thread.current['DRb']['server'] == self
Thread.current['DRb']['stop_service'] = true
else
if @protocol.respond_to? :shutdown
@protocol.shutdown
else
[@thread, *@grp.list].each {|thread| thread.kill} # xxx: Thread#kill
end
@thread.join
end
end
# Convert a dRuby reference to the local object it refers to.
def to_obj(ref)
return front if ref.nil?
return front[ref.to_s] if DRbURIOption === ref
@idconv.to_obj(ref)
end
# Convert a local object to a dRuby reference.
def to_id(obj)
return nil if obj.__id__ == front.__id__
@idconv.to_id(obj)
end
private
##
# Starts the DRb main loop in a new thread.
def run
Thread.start do
begin
while main_loop
end
ensure
@protocol.close if @protocol
end
end
end
# List of insecure methods.
#
# These methods are not callable via dRuby.
INSECURE_METHOD = [
:__send__
]
# Has a method been included in the list of insecure methods?
def insecure_method?(msg_id)
INSECURE_METHOD.include?(msg_id)
end
# Coerce an object to a string, providing our own representation if
# to_s is not defined for the object.
def any_to_s(obj)
obj.to_s + ":#{obj.class}"
rescue
sprintf("#<%s:0x%lx>", obj.class, obj.__id__)
end
# Check that a method is callable via dRuby.
#
# +obj+ is the object we want to invoke the method on. +msg_id+ is the
# method name, as a Symbol.
#
# If the method is an insecure method (see #insecure_method?) a
# SecurityError is thrown. If the method is private or undefined,
# a NameError is thrown.
def check_insecure_method(obj, msg_id)
return true if Proc === obj && msg_id == :__drb_yield
raise(ArgumentError, "#{any_to_s(msg_id)} is not a symbol") unless Symbol == msg_id.class
raise(SecurityError, "insecure method `#{msg_id}'") if insecure_method?(msg_id)
if obj.private_methods.include?(msg_id)
desc = any_to_s(obj)
raise NoMethodError, "private method `#{msg_id}' called for #{desc}"
elsif obj.protected_methods.include?(msg_id)
desc = any_to_s(obj)
raise NoMethodError, "protected method `#{msg_id}' called for #{desc}"
else
true
end
end
public :check_insecure_method
class InvokeMethod # :nodoc:
def initialize(drb_server, client)
@drb_server = drb_server
@safe_level = drb_server.safe_level
@client = client
end
def perform
@result = nil
@succ = false
setup_message
if $SAFE < @safe_level
info = Thread.current['DRb']
if @block
@result = Thread.new {
Thread.current['DRb'] = info
$SAFE = @safe_level
perform_with_block
}.value
else
@result = Thread.new {
Thread.current['DRb'] = info
$SAFE = @safe_level
perform_without_block
}.value
end
else
if @block
@result = perform_with_block
else
@result = perform_without_block
end
end
@succ = true
if @msg_id == :to_ary && @result.class == Array
@result = DRbArray.new(@result)
end
return @succ, @result
rescue StandardError, ScriptError, Interrupt
@result = $!
return @succ, @result
end
private
def init_with_client
obj, msg, argv, block = @client.recv_request
@obj = obj
@msg_id = msg.intern
@argv = argv
@block = block
end
def check_insecure_method
@drb_server.check_insecure_method(@obj, @msg_id)
end
def setup_message
init_with_client
check_insecure_method
end
def perform_without_block
if Proc === @obj && @msg_id == :__drb_yield
if @argv.size == 1
ary = @argv
else
ary = [@argv]
end
ary.collect(&@obj)[0]
else
@obj.__send__(@msg_id, *@argv)
end
end
end
require 'drb/invokemethod'
class InvokeMethod
include InvokeMethod18Mixin
end
# The main loop performed by a DRbServer's internal thread.
#
# Accepts a connection from a client, and starts up its own
# thread to handle it. This thread loops, receiving requests
# from the client, invoking them on a local object, and
# returning responses, until the client closes the connection
# or a local method call fails.
def main_loop
client0 = @protocol.accept
return nil if !client0
Thread.start(client0) do |client|
@grp.add Thread.current
Thread.current['DRb'] = { 'client' => client ,
'server' => self }
DRb.mutex.synchronize do
client_uri = client.uri
@exported_uri << client_uri unless @exported_uri.include?(client_uri)
end
loop do
begin
succ = false
invoke_method = InvokeMethod.new(self, client)
succ, result = invoke_method.perform
if !succ && verbose
p result
result.backtrace.each do |x|
puts x
end
end
client.send_reply(succ, result) rescue nil
ensure
client.close unless succ
if Thread.current['DRb']['stop_service']
Thread.new { stop_service }
end
break unless succ
end
end
end
end
end
@primary_server = nil
# Start a dRuby server locally.
#
# The new dRuby server will become the primary server, even
# if another server is currently the primary server.
#
# +uri+ is the URI for the server to bind to. If nil,
# the server will bind to random port on the default local host
# name and use the default dRuby protocol.
#
# +front+ is the server's front object. This may be nil.
#
# +config+ is the configuration for the new server. This may
# be nil.
#
# See DRbServer::new.
def start_service(uri=nil, front=nil, config=nil)
@primary_server = DRbServer.new(uri, front, config)
end
module_function :start_service
# The primary local dRuby server.
#
# This is the server created by the #start_service call.
attr_accessor :primary_server
module_function :primary_server=, :primary_server
# Get the 'current' server.
#
# In the context of execution taking place within the main
# thread of a dRuby server (typically, as a result of a remote
# call on the server or one of its objects), the current
# server is that server. Otherwise, the current server is
# the primary server.
#
# If the above rule fails to find a server, a DRbServerNotFound
# error is raised.
def current_server
drb = Thread.current['DRb']
server = (drb && drb['server']) ? drb['server'] : @primary_server
raise DRbServerNotFound unless server
return server
end
module_function :current_server
# Stop the local dRuby server.
#
# This operates on the primary server. If there is no primary
# server currently running, it is a noop.
def stop_service
@primary_server.stop_service if @primary_server
@primary_server = nil
end
module_function :stop_service
# Get the URI defining the local dRuby space.
#
# This is the URI of the current server. See #current_server.
def uri
drb = Thread.current['DRb']
client = (drb && drb['client'])
if client
uri = client.uri
return uri if uri
end
current_server.uri
end
module_function :uri
# Is +uri+ the URI for the current local server?
def here?(uri)
current_server.here?(uri) rescue false
# (current_server.uri rescue nil) == uri
end
module_function :here?
# Get the configuration of the current server.
#
# If there is no current server, this returns the default configuration.
# See #current_server and DRbServer::make_config.
def config
current_server.config
rescue
DRbServer.make_config
end
module_function :config
# Get the front object of the current server.
#
# This raises a DRbServerNotFound error if there is no current server.
# See #current_server.
def front
current_server.front
end
module_function :front
# Convert a reference into an object using the current server.
#
# This raises a DRbServerNotFound error if there is no current server.
# See #current_server.
def to_obj(ref)
current_server.to_obj(ref)
end
# Get a reference id for an object using the current server.
#
# This raises a DRbServerNotFound error if there is no current server.
# See #current_server.
def to_id(obj)
current_server.to_id(obj)
end
module_function :to_id
module_function :to_obj
# Get the thread of the primary server.
#
# This returns nil if there is no primary server. See #primary_server.
def thread
@primary_server ? @primary_server.thread : nil
end
module_function :thread
# Set the default id conversion object.
#
# This is expected to be an instance such as DRb::DRbIdConv that responds to
# #to_id and #to_obj that can convert objects to and from DRb references.
#
# See DRbServer#default_id_conv.
def install_id_conv(idconv)
DRbServer.default_id_conv(idconv)
end
module_function :install_id_conv
# Set the default ACL to +acl+.
#
# See DRb::DRbServer.default_acl.
def install_acl(acl)
DRbServer.default_acl(acl)
end
module_function :install_acl
@mutex = Mutex.new
def mutex # :nodoc:
@mutex
end
module_function :mutex
@server = {}
# Registers +server+ with DRb.
#
# This is called when a new DRb::DRbServer is created.
#
# If there is no primary server then +server+ becomes the primary server.
#
# Example:
#
# require 'drb'
#
# s = DRb::DRbServer.new # automatically calls regist_server
# DRb.fetch_server s.uri #=> #<DRb::DRbServer:0x...>
def regist_server(server)
@server[server.uri] = server
mutex.synchronize do
@primary_server = server unless @primary_server
end
end
module_function :regist_server
# Removes +server+ from the list of registered servers.
def remove_server(server)
@server.delete(server.uri)
end
module_function :remove_server
# Retrieves the server with the given +uri+.
#
# See also regist_server and remove_server.
def fetch_server(uri)
@server[uri]
end
module_function :fetch_server
end
# :stopdoc:
DRbObject = DRb::DRbObject
DRbUndumped = DRb::DRbUndumped
DRbIdConv = DRb::DRbIdConv
|