1- from collections import defaultdict
2- from ctypes import cdll , c_int , c_float , byref , POINTER
3- from ctypes .util import find_library
4- import os
5- import platform
1+ from ctypes import byref , c_float , c_int , POINTER
62import random
7- import re
8- import shutil
9- import subprocess
10- import tempfile
11- import uuid
123import warnings
134
145import axelrod as axl
156from axelrod .interaction_utils import compute_final_score
167from axelrod .action import Action
8+
179from .strategies import characteristics
10+ from .shared_library_manager import MultiprocessManager , load_library
1811
1912C , D = Action .C , Action .D
2013actions = {0 : C , 1 : D }
2114original_actions = {C : 0 , D : 1 }
2215
2316
24- path_regex = r""".*?\s=>\s(.*?{}.*?)\\"""
25-
2617self_interaction_message = """
2718You are playing a match with the same player against itself. However
2819axelrod_fortran players share memory. You can initialise another instance of an
2920Axelrod_fortran player with player.clone().
3021"""
3122
3223
33- class LibraryManager (object ):
34- """LibraryManager creates and loads copies of a shared library, which
35- enables multiple copies of the same strategy to be run without the end user
36- having to maintain many copies of the shared library.
37-
38- This works by making a copy of the shared library file and loading it into
39- memory again. Loading the same file again will return a reference to the
40- same memory addresses.
41-
42- Additionally, library manager tracks how many copies of the library have
43- been loaded, and how many copies there are of each Player, so as to load
44- only as many copies of the shared library as needed.
45- """
46-
47- def __init__ (self , shared_library_name , verbose = False ):
48- self .shared_library_name = shared_library_name
49- self .verbose = verbose
50- self .library_copies = []
51- self .player_indices = defaultdict (set )
52- self .player_next = defaultdict (set )
53- # Generate a random prefix for tempfile generation
54- self .prefix = str (uuid .uuid4 ())
55- self .library_path = self .find_shared_library (shared_library_name )
56- self .filenames = []
57-
58- def find_shared_library (self , shared_library_name ):
59- # Hack for Linux since find_library doesn't return the full path.
60- if 'Linux' in platform .system ():
61- output = subprocess .check_output (["ldconfig" , "-p" ])
62- for line in str (output ).split (r"\n" ):
63- rhs = line .split (" => " )[- 1 ]
64- if shared_library_name in rhs :
65- return rhs
66- raise ValueError ("{} not found" .format (shared_library_name ))
67- else :
68- return find_library (
69- shared_library_name .replace ("lib" , "" ).replace (".so" , "" ))
70-
71- def load_dll_copy (self ):
72- """Load a new copy of the shared library."""
73- # Copy the library file to a new location so we can load the copy.
74- temp_directory = tempfile .gettempdir ()
75- copy_number = len (self .library_copies )
76- new_filename = os .path .join (
77- temp_directory ,
78- "{}-{}-{}" .format (
79- self .prefix ,
80- str (copy_number ),
81- self .shared_library_name )
82- )
83- if self .verbose :
84- print ("Loading {}" .format (new_filename ))
85- shutil .copy2 (self .library_path , new_filename )
86- self .filenames .append (new_filename )
87- shared_library = cdll .LoadLibrary (new_filename )
88- self .library_copies .append (shared_library )
89-
90- def next_player_index (self , name ):
91- """Determine the index of the next free shared library copy to
92- allocate for the player. If none is available then load another copy."""
93- # Is there a free index?
94- if len (self .player_next [name ]) > 0 :
95- return self .player_next [name ].pop ()
96- # Do we need to load a new copy?
97- player_count = len (self .player_indices [name ])
98- if player_count == len (self .library_copies ):
99- self .load_dll_copy ()
100- return player_count
101- # Find the first unused index
102- for i in range (len (self .library_copies )):
103- if i not in self .player_indices [name ]:
104- return i
105- raise ValueError ("We shouldn't be here." )
106-
107- def load_library_for_player (self , name ):
108- """For a given player return a copy of the shared library for use
109- in a Player class, along with an index for later releasing."""
110- index = self .next_player_index (name )
111- self .player_indices [name ].add (index )
112- if self .verbose :
113- print ("allocating {}" .format (index ))
114- return index , self .library_copies [index ]
115-
116- def release (self , name , index ):
117- """Release the copy of the library so that it can be re-allocated."""
118- self .player_indices [name ].remove (index )
119- if self .verbose :
120- print ("releasing {}" .format (index ))
121- self .player_next [name ].add (index )
122-
123- def __del__ (self ):
124- """Cleanup temp files on object deletion."""
125- for filename in self .filenames :
126- if os .path .exists (filename ):
127- os .remove (filename )
24+ # Initialize a module-wide manager for loading copies of the shared library.
25+ manager = MultiprocessManager ()
26+ manager .start ()
27+ shared_library_manager = manager .SharedLibraryManager ("libstrategies.so" )
12828
12929
13030class Player (axl .Player ):
13131
13232 classifier = {"stochastic" : True }
133- library_manager = None
13433
135- def __init__ (self , original_name ,
136- shared_library_name = 'libstrategies.so' ):
34+ def __init__ (self , original_name ):
13735 """
13836 Parameters
13937 ----------
@@ -144,17 +42,16 @@ def __init__(self, original_name,
14442 A instance of an axelrod Game
14543 """
14644 super ().__init__ ()
147- if not Player .library_manager :
148- Player .library_manager = LibraryManager (shared_library_name )
149- self .index , self .shared_library = \
150- self .library_manager .load_library_for_player (original_name )
45+ self .index , self .shared_library_filename = \
46+ shared_library_manager .get_filename_for_player (original_name )
47+ self .shared_library = load_library (self .shared_library_filename )
15148 self .original_name = original_name
15249 self .original_function = self .original_name
153-
15450 is_stochastic = characteristics [self .original_name ]['stochastic' ]
15551 if is_stochastic is not None :
15652 self .classifier ['stochastic' ] = is_stochastic
15753
54+
15855 def __enter__ (self ):
15956 return self
16057
@@ -216,15 +113,21 @@ def strategy(self, opponent):
216113 my_last_move )
217114 return actions [original_action ]
218115
116+ def _release_shared_library (self ):
117+ try :
118+ shared_library_manager .release (self .original_name , self .index )
119+ except FileNotFoundError :
120+ pass
121+
219122 def reset (self ):
220- # Release the library before rest, which regenerates the player .
221- self .library_manager . release ( self . original_name , self . index )
123+ # Release the shared library since the object is rebuilt on reset .
124+ self ._release_shared_library ( )
222125 super ().reset ()
223126 self .original_function = self .original_name
224127
225128 def __del__ (self ):
226129 # Release the library before deletion.
227- self .library_manager . release ( self . original_name , self . index )
130+ self ._release_shared_library ( )
228131
229132 def __repr__ (self ):
230133 return self .original_name
0 commit comments