From 8ee129e6beacc0f4e3c1107892678180d6c5eb1c Mon Sep 17 00:00:00 2001 From: zaro Date: Mon, 4 Mar 2013 16:00:20 -0800 Subject: [PATCH] make thread safe This checkin attempts to make gearman plugin objects thread safe. src/main/java/hudson/plugins/gearman/SaveableListenerImpl.java Decided that this was not needed. src/main/java/hudson/plugins/gearman/GearmanPluginConfig.java Added method to allow other objects to access gearman server launchWorker, host, and port settings src/main/java/hudson/plugins/gearman/GearmanPluginUtil.java Useful utilities for this plugin src/main/java/hudson/plugins/gearman/AbstractWorkerThread.java src/main/java/hudson/plugins/gearman/ComputerListenerImpl.java src/main/java/hudson/plugins/gearman/ProjectListener.java src/main/java/hudson/plugins/gearman/GearmanProxy.java Made object private and added syncronized setters and getters Change-Id: I04ca6a275ba8184bd18cf8954d07b94d02b2a47d --- .../plugins/gearman/AbstractWorkerThread.java | 15 +- .../plugins/gearman/ComputerListenerImpl.java | 175 ++++++++++-------- .../plugins/gearman/GearmanPluginConfig.java | 25 ++- .../plugins/gearman/GearmanPluginUtil.java | 70 +++++-- .../hudson/plugins/gearman/GearmanProxy.java | 71 +++++-- .../plugins/gearman/ProjectListener.java | 104 +++++++---- .../plugins/gearman/SaveableListenerImpl.java | 25 --- 7 files changed, 297 insertions(+), 188 deletions(-) delete mode 100644 src/main/java/hudson/plugins/gearman/SaveableListenerImpl.java diff --git a/src/main/java/hudson/plugins/gearman/AbstractWorkerThread.java b/src/main/java/hudson/plugins/gearman/AbstractWorkerThread.java index cddfc1c..e6dd2cd 100644 --- a/src/main/java/hudson/plugins/gearman/AbstractWorkerThread.java +++ b/src/main/java/hudson/plugins/gearman/AbstractWorkerThread.java @@ -38,8 +38,6 @@ import org.slf4j.LoggerFactory; public abstract class AbstractWorkerThread implements Runnable { public static final String DEFAULT_EXECUTOR_NAME = "anonymous"; -// private static final Logger logger = LoggerFactory -// .getLogger(Constants.PLUGIN_EXECTUOR_LOGGER_NAME); private static final Logger logger = LoggerFactory .getLogger(Constants.PLUGIN_LOGGER_NAME); @@ -123,10 +121,15 @@ public abstract class AbstractWorkerThread implements Runnable { if (worker.isRunning()) { - logger.info("Stopping " + getName() + ":" + getId().toString() + - " (" + new Date().toString() + ")"); - worker.unregisterAll(); - worker.stop(); + try { + logger.info("Stopping " + getName() + ":" + getId().toString() + + " (" + new Date().toString() + ")"); + worker.unregisterAll(); + worker.stop(); + } catch (Exception e) { + e.printStackTrace(); + } + } thread.interrupt(); diff --git a/src/main/java/hudson/plugins/gearman/ComputerListenerImpl.java b/src/main/java/hudson/plugins/gearman/ComputerListenerImpl.java index 490fa2e..06bb961 100644 --- a/src/main/java/hudson/plugins/gearman/ComputerListenerImpl.java +++ b/src/main/java/hudson/plugins/gearman/ComputerListenerImpl.java @@ -10,8 +10,7 @@ import hudson.slaves.ComputerListener; import hudson.slaves.OfflineCause; import java.io.IOException; - -import jenkins.model.Jenkins; +import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,152 +25,166 @@ public class ComputerListenerImpl extends ComputerListener { .getLogger(Constants.PLUGIN_LOGGER_NAME); @Override - public void preOnline(Computer c, Channel channel, FilePath root, TaskListener listener) throws IOException, InterruptedException { + public void preOnline(Computer c, Channel channel, FilePath root, + TaskListener listener) throws IOException, InterruptedException { // called when slave re-connects // called when new slaves are connecting for first time - logger.info("---- "+ComputerListenerImpl.class.getName()+":"+" preOnline"); + logger.info("---- " + ComputerListenerImpl.class.getName() + ":" + + " preOnline"); - if (!GearmanPluginConfig.launchWorker) { + // update functions only when gearman-plugin is enabled + if (!GearmanPluginConfig.get().launchWorker()) { return; } - // on creation of slave - int currNumNodes = Jenkins.getInstance().getNodes().size(); - if (GearmanProxy.numExecutorNodes < currNumNodes) { - Node node = c.getNode(); - int slaveExecutors = c.getExecutors().size(); - for (int i=0; i currNumNodes) { - if (!GearmanProxy.gewtHandles.isEmpty()) { - GearmanProxy.numExecutorNodes--; - logger.info("---- numExecutorNodes = "+GearmanProxy.numExecutorNodes); - for (AbstractWorkerThread awt: GearmanProxy.gewtHandles) { - if (awt.name.contains(c.getName())) { - try { - awt.stop(); - }catch (Exception e){ - e.printStackTrace(); - } - GearmanProxy.gewtHandles.remove(awt); + int prevNumNodes = GearmanProxy.getNumWorkerNodes(); + int currNumNodes = GearmanPluginUtil.getNumTotalNodes(); + logger.info("---- prevNumNodes = " + prevNumNodes); + logger.info("---- currNumNodes = " + currNumNodes); + if (prevNumNodes > currNumNodes) { + List workers = GearmanProxy.getGewtHandles(); + if (!workers.isEmpty()) { + for (AbstractWorkerThread worker : workers) { + if (worker.name.contains(c.getName())) { + logger.info("---- stopping executor worker = " + + worker.getName()); + GearmanProxy.getGewtHandles().remove(worker); + worker.stop(); } } + GearmanProxy.setNumWorkerNodes(currNumNodes); + logger.info("---- numWorkerNodes = " + + GearmanProxy.getNumWorkerNodes()); } - } - - // on disconnect of node - if (!GearmanProxy.gewtHandles.isEmpty()) { - for (AbstractWorkerThread awt: GearmanProxy.gewtHandles) { - awt.registerJobs(); + } else { + // on disconnect of node + // update gearman worker functions on existing threads + List workers = GearmanProxy.getGewtHandles(); + if (!workers.isEmpty()) { + for (AbstractWorkerThread worker : workers) { + worker.registerJobs(); + } } } } @Override - public void onOnline(Computer c, TaskListener listener) throws IOException, InterruptedException { + public void onOnline(Computer c, TaskListener listener) throws IOException, + InterruptedException { // gets called when existing slave re-connects - // gets called when new slave is online. - logger.info("---- "+ComputerListenerImpl.class.getName()+":"+" onOnline"); + // gets called when new slave goes into online state. + logger.info("---- " + ComputerListenerImpl.class.getName() + ":" + + " onOnline"); - if (!GearmanPluginConfig.launchWorker) { + // update functions only when gearman-plugin is enabled + if (!GearmanPluginConfig.get().launchWorker()) { return; } // on re-connection of node - if (!GearmanProxy.gewtHandles.isEmpty()) { - for (AbstractWorkerThread awt: GearmanProxy.gewtHandles) { - awt.registerJobs(); + // update gearman worker functions on existing threads + List workers = GearmanProxy.getGewtHandles(); + if (!workers.isEmpty()) { + for (AbstractWorkerThread worker : workers) { + worker.registerJobs(); } } - } @Override public void onTemporarilyOnline(Computer c) { // gets called when existing slave is re-enabled - logger.info("---- "+ComputerListenerImpl.class.getName()+":"+" onTemporarilyOnline"); + logger.info("---- " + ComputerListenerImpl.class.getName() + ":" + + " onTemporarilyOnline"); - if (!GearmanPluginConfig.launchWorker) { + // update functions only when gearman-plugin is enabled + if (!GearmanPluginConfig.get().launchWorker()) { return; } - if (!GearmanProxy.gewtHandles.isEmpty()) { - for (AbstractWorkerThread awt: GearmanProxy.gewtHandles) { - awt.registerJobs(); + // update gearman worker functions on existing threads + List workers = GearmanProxy.getGewtHandles(); + if (!workers.isEmpty()) { + for (AbstractWorkerThread worker : workers) { + worker.registerJobs(); } } - } @Override public void onTemporarilyOffline(Computer c, OfflineCause cause) { // gets called when existing slave is dis-enabled - logger.info("---- "+ComputerListenerImpl.class.getName()+":"+" onTemporarilyOffline"); + logger.info("---- " + ComputerListenerImpl.class.getName() + ":" + + " onTemporarilyOffline"); - if (!GearmanPluginConfig.launchWorker) { + // update functions only when gearman-plugin is enabled + if (!GearmanPluginConfig.get().launchWorker()) { return; } - if (!GearmanProxy.gewtHandles.isEmpty()) { - for (AbstractWorkerThread awt: GearmanProxy.gewtHandles) { - awt.registerJobs(); + // update gearman worker functions on existing threads + List workers = GearmanProxy.getGewtHandles(); + if (!workers.isEmpty()) { + for (AbstractWorkerThread worker : workers) { + worker.registerJobs(); } } } - } diff --git a/src/main/java/hudson/plugins/gearman/GearmanPluginConfig.java b/src/main/java/hudson/plugins/gearman/GearmanPluginConfig.java index 519db6d..89b82ab 100644 --- a/src/main/java/hudson/plugins/gearman/GearmanPluginConfig.java +++ b/src/main/java/hudson/plugins/gearman/GearmanPluginConfig.java @@ -35,7 +35,7 @@ import org.slf4j.LoggerFactory; /** * This class is used to set the global configuration for the gearman-plugin It - * is also used to launch gearman workers on this Jenkins server + * is also used to enable/disable the gearman plugin. * * @author Khai Do */ @@ -44,12 +44,11 @@ public class GearmanPluginConfig extends GlobalConfiguration { private static final Logger logger = LoggerFactory .getLogger(Constants.PLUGIN_LOGGER_NAME); - public static boolean launchWorker; // launchWorker state (from UI checkbox) + private boolean launchWorker; // enable/disable plugin private String host; // gearman server host private int port; // gearman server port GearmanProxy gearmanProxy; - /** * Constructor. */ @@ -64,16 +63,18 @@ public class GearmanPluginConfig extends GlobalConfiguration { * initialize the launch worker flag to disabled state at jenkins * startup so we are always at a known state */ - GearmanPluginConfig.launchWorker = Constants.GEARMAN_DEFAULT_LAUNCH_WORKER; + launchWorker = Constants.GEARMAN_DEFAULT_LAUNCH_WORKER; save(); } + public static GearmanPluginConfig get() { + return GlobalConfiguration.all().get(GearmanPluginConfig.class); + } /* * This method runs when user clicks Test Connection button. * - * @return - * message indicating whether connection test passed or failed + * @return message indicating whether connection test passed or failed */ public FormValidation doTestConnection( @QueryParameter("host") final String host, @@ -87,11 +88,15 @@ public class GearmanPluginConfig extends GlobalConfiguration { } } + /* + * This method runs when user saves the configuration form + */ @Override public boolean configure(StaplerRequest req, JSONObject json) throws Descriptor.FormException { - // set the gearman config from user entered values in jenkins config page + // set the gearman config from user entered values in jenkins config + // page launchWorker = json.getBoolean("launchWorker"); host = json.getString("host"); port = json.getInt("port"); @@ -102,8 +107,10 @@ public class GearmanPluginConfig extends GlobalConfiguration { logger.info("--- Check connection to Gearman Server " + host + ":" + port); if (!GearmanPluginUtil.connectionIsAvailable(host, port, 5000)) { - GearmanPluginConfig.launchWorker = false; - throw new RuntimeException("Unable to connect to Gearman Server"); + launchWorker = false; + throw new FormException("Unable to connect to Gearman server. " + + "Please check the server connection settings and retry.", + "host"); } gearmanProxy.init_worker(host, port); diff --git a/src/main/java/hudson/plugins/gearman/GearmanPluginUtil.java b/src/main/java/hudson/plugins/gearman/GearmanPluginUtil.java index 0aee4b2..c3cfd98 100644 --- a/src/main/java/hudson/plugins/gearman/GearmanPluginUtil.java +++ b/src/main/java/hudson/plugins/gearman/GearmanPluginUtil.java @@ -1,33 +1,57 @@ +/* + * + * Copyright 2013 Hewlett-Packard Development Company, L.P. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + package hudson.plugins.gearman; +import hudson.model.Computer; +import hudson.model.Node; + import java.net.InetSocketAddress; import java.net.Socket; +import jenkins.model.Jenkins; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * This class contains some useful utilities for this plugin + * + * @author Khai Do + */ public class GearmanPluginUtil { private static final Logger logger = LoggerFactory .getLogger(Constants.PLUGIN_LOGGER_NAME); - /* * This method checks whether a connection can be made to a host:port * - * @param host - * the host name + * @param host the host name * - * @param port - * the host port + * @param port the host port * - * @param timeout - * the timeout (milliseconds) to try the connection + * @param timeout the timeout (milliseconds) to try the connection * - * @return - * true if a socket connection can be established otherwise false + * @return true if a socket connection can be established otherwise false */ - public static boolean connectionIsAvailable(String host, int port, int timeout) { + public static boolean connectionIsAvailable(String host, int port, + int timeout) { InetSocketAddress endPoint = new InetSocketAddress(host, port); Socket socket = new Socket(); @@ -37,12 +61,11 @@ public class GearmanPluginUtil { } else { try { socket.connect(endPoint, timeout); - logger.info("Connection Success: "+endPoint); + logger.info("Connection Success: " + endPoint); return true; } catch (Exception e) { - logger.info("Connection Failure: "+endPoint+" message: " - +e.getClass().getSimpleName()+" - " - +e.getMessage()); + logger.info("Connection Failure: " + endPoint + " message: " + + e.getClass().getSimpleName() + " - " + e.getMessage()); } finally { if (socket != null) { try { @@ -56,5 +79,24 @@ public class GearmanPluginUtil { return false; } + /* + * This method returns the total number of nodes that are active in jenkins, + * active mean that it's been created, but not necessarily online. + */ + public static int getNumTotalNodes() { + + // check whether master is enabled + Node masterNode = null; + try { + masterNode = Computer.currentComputer().getNode(); + } catch (Exception e) { + } + + if (masterNode != null) { // master is enabled, count it + return Jenkins.getInstance().getNodes().size() + 1; + } else { // only slaves, no master + return Jenkins.getInstance().getNodes().size(); + } + } } diff --git a/src/main/java/hudson/plugins/gearman/GearmanProxy.java b/src/main/java/hudson/plugins/gearman/GearmanProxy.java index 25f3179..4d0cac4 100644 --- a/src/main/java/hudson/plugins/gearman/GearmanProxy.java +++ b/src/main/java/hudson/plugins/gearman/GearmanProxy.java @@ -16,7 +16,6 @@ * */ - package hudson.plugins.gearman; import hudson.model.Computer; @@ -30,28 +29,44 @@ import jenkins.model.Jenkins; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * This class is used to startup and shutdown the gearman workers. + * It is also used to keep gearman plugin state info. + * + * @author Khai Do + */ public class GearmanProxy { private static final Logger logger = LoggerFactory .getLogger(Constants.PLUGIN_LOGGER_NAME); - // handles to gearman workers - public static List gewtHandles; - public static List gmwtHandles; + private static List gewtHandles; + private static List gmwtHandles; - public static int numExecutorNodes; + // keep track of number of executor slaves in system + private static int numWorkerNodes; + + // constructor public GearmanProxy() { logger.info("--- GearmanProxy Constructor ---"); gewtHandles = new Stack(); gmwtHandles = new Stack(); - numExecutorNodes = 0; - + numWorkerNodes = 0; } - public void init_worker(String host, int port) throws RuntimeException{ + + /* + * This method initializes the gearman workers. + * + * @param host the host name + * + * @param port the host port + * + */ + public void init_worker(String host, int port) { /* * Purpose here is to create a 1:1 mapping of 'gearman worker':'jenkins @@ -96,7 +111,7 @@ public class GearmanProxy { gwt.start(); gewtHandles.add(gwt); } - numExecutorNodes++; + numWorkerNodes++; } /* @@ -116,16 +131,20 @@ public class GearmanProxy { gwt.start(); gewtHandles.add(gwt); } - numExecutorNodes++; + numWorkerNodes++; } } } - logger.info("--- Num of executors running = "+getNumExecutors()); + logger.info("--- Num of executors running = " + getNumExecutors()); } + + /* + * This method stops all gearman workers + */ public void stop_all() { - //stop gearman executors + // stop gearman executors for (AbstractWorkerThread gewtHandle : gewtHandles) { // stop executors gewtHandle.stop(); } @@ -135,15 +154,37 @@ public class GearmanProxy { gmwtHandle.stop(); } gmwtHandles.clear(); - numExecutorNodes = 0; + numWorkerNodes = 0; - logger.info("--- Num of executors running = "+getNumExecutors()); + logger.info("--- Num of executors running = " + getNumExecutors()); } + /* + * This method returns the total number of gearman executor threads + */ public int getNumExecutors() { + return gmwtHandles.size() + gewtHandles.size(); + } - return gmwtHandles.size()+gewtHandles.size(); + /* + * This method returns the list of gearman executor workers + */ + public static synchronized List getGewtHandles() { + return gewtHandles; + } + /* + * This method returns the number of worker nodes + */ + public static synchronized int getNumWorkerNodes() { + return numWorkerNodes; + } + + /* + * This method sets the number of worker nodes + */ + public static synchronized void setNumWorkerNodes(int numWorkerNodes) { + GearmanProxy.numWorkerNodes = numWorkerNodes; } } diff --git a/src/main/java/hudson/plugins/gearman/ProjectListener.java b/src/main/java/hudson/plugins/gearman/ProjectListener.java index f2cbc73..062f42a 100644 --- a/src/main/java/hudson/plugins/gearman/ProjectListener.java +++ b/src/main/java/hudson/plugins/gearman/ProjectListener.java @@ -1,10 +1,29 @@ -package hudson.plugins.gearman; +/* + * + * Copyright 2013 Hewlett-Packard Development Company, L.P. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package hudson.plugins.gearman; import hudson.Extension; import hudson.model.Item; import hudson.model.listeners.ItemListener; +import java.util.List; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -12,93 +31,102 @@ import org.slf4j.LoggerFactory; * Update gearman workers when project changes */ @Extension -public class ProjectListener extends ItemListener -{ +public class ProjectListener extends ItemListener { private static final Logger logger = LoggerFactory .getLogger(Constants.PLUGIN_LOGGER_NAME); @Override - public void onUpdated(Item item) - { - logger.info("---- "+ProjectListener.class.getName()+":"+" onUpdated"); + public void onUpdated(Item item) { + logger.info("---- " + ProjectListener.class.getName() + ":" + + " onUpdated"); - if (!GearmanPluginConfig.launchWorker) { + // update functions only when gearman-plugin is enabled + if (!GearmanPluginConfig.get().launchWorker()) { return; } // update gearman worker functions on existing threads - if (!GearmanProxy.gewtHandles.isEmpty()) { - for (AbstractWorkerThread awt: GearmanProxy.gewtHandles) { - awt.registerJobs(); + List workers = GearmanProxy.getGewtHandles(); + if (!workers.isEmpty()) { + for (AbstractWorkerThread worker : workers) { + worker.registerJobs(); } } } @Override - public void onRenamed(Item item, String oldName, String newName) - { - logger.info("---- "+ProjectListener.class.getName()+":"+" onRenamed"); + public void onRenamed(Item item, String oldName, String newName) { + logger.info("---- " + ProjectListener.class.getName() + ":" + + " onRenamed"); - if (!GearmanPluginConfig.launchWorker) { + // update functions only when gearman-plugin is enabled + if (!GearmanPluginConfig.get().launchWorker()) { return; } // update gearman worker functions on existing threads - if (!GearmanProxy.gewtHandles.isEmpty()) { - for (AbstractWorkerThread awt: GearmanProxy.gewtHandles) { - awt.registerJobs(); + List workers = GearmanProxy.getGewtHandles(); + if (!workers.isEmpty()) { + for (AbstractWorkerThread worker : workers) { + worker.registerJobs(); } } } @Override - public void onDeleted(Item item) - { - logger.info("---- "+ProjectListener.class.getName()+":"+" onDeleted"); + public void onDeleted(Item item) { + logger.info("---- " + ProjectListener.class.getName() + ":" + + " onDeleted"); - if (!GearmanPluginConfig.launchWorker) { + // update functions only when gearman-plugin is enabled + if (!GearmanPluginConfig.get().launchWorker()) { return; } // update gearman worker functions on existing threads - if (!GearmanProxy.gewtHandles.isEmpty()) { - for (AbstractWorkerThread awt: GearmanProxy.gewtHandles) { - awt.registerJobs(); + List workers = GearmanProxy.getGewtHandles(); + if (!workers.isEmpty()) { + for (AbstractWorkerThread worker : workers) { + worker.registerJobs(); } } } @Override - public void onCreated(Item item) - { - logger.info("---- "+ProjectListener.class.getName()+":"+" onCreated"); + public void onCreated(Item item) { + logger.info("---- " + ProjectListener.class.getName() + ":" + + " onCreated"); - if (!GearmanPluginConfig.launchWorker) { + // update functions only when gearman-plugin is enabled + if (!GearmanPluginConfig.get().launchWorker()) { return; } // update gearman worker functions on existing threads - if (!GearmanProxy.gewtHandles.isEmpty()) { - for (AbstractWorkerThread awt: GearmanProxy.gewtHandles) { - awt.registerJobs(); + List workers = GearmanProxy.getGewtHandles(); + if (!workers.isEmpty()) { + for (AbstractWorkerThread worker : workers) { + worker.registerJobs(); } } } @Override - public void onCopied(Item src, Item item) - { - logger.info("---- "+ProjectListener.class.getName()+":"+" onCopied"); + public void onCopied(Item src, Item item) { + logger.info("---- " + ProjectListener.class.getName() + ":" + + " onCopied"); - if (!GearmanPluginConfig.launchWorker) { + // update functions only when gearman-plugin is enabled + if (!GearmanPluginConfig.get().launchWorker()) { return; } // update gearman worker functions on existing threads - if (!GearmanProxy.gewtHandles.isEmpty()) { - for (AbstractWorkerThread awt: GearmanProxy.gewtHandles) { - awt.registerJobs(); + List workers = GearmanProxy.getGewtHandles(); + if (!workers.isEmpty()) { + for (AbstractWorkerThread worker : workers) { + worker.registerJobs(); } } } diff --git a/src/main/java/hudson/plugins/gearman/SaveableListenerImpl.java b/src/main/java/hudson/plugins/gearman/SaveableListenerImpl.java deleted file mode 100644 index 87b2138..0000000 --- a/src/main/java/hudson/plugins/gearman/SaveableListenerImpl.java +++ /dev/null @@ -1,25 +0,0 @@ -package hudson.plugins.gearman; - -import hudson.XmlFile; -import hudson.model.Saveable; -import hudson.model.listeners.SaveableListener; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class SaveableListenerImpl extends SaveableListener { - - private static final Logger logger = LoggerFactory - .getLogger(Constants.PLUGIN_LOGGER_NAME); - - @Override - public void onChange(Saveable o, XmlFile file) { - logger.info("---- "+SaveableListenerImpl.class.getName()+":"+" onChange"); -// AbstractProject ab = (AbstractProject)o; -// logger.info("----"+ab.getName()); - - } - - - -}